Picsha AI

Enriched Agentic Webhooks

Traditional webhooks are purely passive notifications—they send a lightweight payload containing nothing more than an ID and a status (e.g. {"assetId": "as_123", "status": "completed"}). For AI agents operating in autonomous frameworks like LangGraph, AutoGen, or CrewAI, this passive approach forces the agent to make multiple sequential API round-trips to pull technical metadata, visual tags, textual summaries, and vector embeddings.

Picsha AI solves this bottleneck with Enriched Agentic Webhooks. When an ingestion pipeline completes, Picsha constructs and dispatches a fully enriched cognitive package containing geocoded location strings, technical details, raw EXIF metadata, AI safety moderation labels (if moderation is enabled), Claude-generated visual summaries, and 1024-dimensional vector embeddings directly to your agent's receiver endpoint.

This enables downstream AI agents to reactively reason over visual and auditory media assets with zero subsequent API round-trips.


1. Supported Webhook Events

Picsha AI triggers webhooks based on the asset lifecycle. You can subscribe to one or more of the following event types:

Event TypeTrigger Condition
asset.processedThe ingest pipeline finished for an asset — successfully or not. Fires for every ingested asset, including URL ingest and text-to-image generation (POST /v1/assets/generate). Always branch on data.status.
face.propagation.startedA face-name assignment has begun propagating across the organization's photo library.
face.propagation.completedFace-name propagation finished; matching assets have been updated.
face.propagation.failedFace-name propagation encountered an unrecoverable error.

Face propagation only ever touches assets of the organization that named the face — or, for a personal account, that account's own assets — and matchCount counts only those faces. Clearing a name (DELETE /v1/rekognition/faces/{faceId}/name) is never propagated, so it emits none of these events.

[!IMPORTANT] asset.processed is a terminal event, not a success event. Until the dedicated failure events ship, pipeline failures arrive on this same event with a reduced payload:

{ "event": "asset.processed",
  "data": { "assetId": "...", "status": "failed", "error": "Unsupported codec" } }

status is "failed" for pipeline errors and "infected" when the ClamAV scan quarantined the file. Check data.status === "completed" before treating a payload as a finished asset.

[!NOTE] Deduplicated uploads emit a smaller payload. When you upload bytes your organization has already uploaded (for a personal account, bytes you have already uploaded yourself), the upload short-circuits and the notification identifies the asset with data.id rather than data.assetId, and carries no aiAnalysis, rawExif, or embedding (the existing asset already has them — fetch it with GET /assets/{id}). Read the id defensively: const assetId = data.assetId ?? data.id.

[!NOTE] Dedicated lifecycle events (asset.queued, asset.active, asset.failed, asset.infected, asset.pending_moderation, asset.rejected) are on the roadmap and will be added to this table as they ship. Subscribing to * receives every current and future event.


2. Selective Enrichment Configuration

To protect client network bandwidth, safeguard user privacy, and prevent serverless gateway payload failures (e.g. AWS API Gateway limits), Picsha implements a Selective Enrichment architecture.

By default, outgoing webhooks include technical specifications, geocoding, visual tags, and Claude summaries. Developers can explicitly toggle the inclusion of large text blocks and float32 vector arrays during webhook registration:

Configuration FlagTypeDefaultDescription
include_embeddingsbooleanfalseWhen enabled, attaches the raw 1024-dimensional Titan Multimodal embedding float array directly to the payload (data.embedding).
include_textbooleanfalseWhen enabled, attaches untruncated visual summaries. When disabled, the long summary is truncated to 500 characters. Note: Extracted OCR and transcript data are bound by a hard safety ceiling of 10,000 characters to prevent endpoint timeouts.

3. Registering a Webhook

You can register a webhook programmatically via our Fastify API gateway. If you register a webhook without providing a custom secret, Picsha automatically generates a cryptographically secure signing secret prefixed with wh_sec_.

Who a subscription belongs to

A subscription is keyed on a pair: an organization and a user id. The pair decides both who can manage the subscription and which events it receives.

You register withThe subscription is keyed by
An Organization API key and x-external-user-idYour organization + that user id
An Organization API key, no headerYour organization + system: an organization-wide subscription
A personal API key (one that does not belong to an Organization)Your own account. x-external-user-id is ignored

GET /v1/webhooks, PATCH, and DELETE only ever see subscriptions registered under the same pair — with an Organization key, send the same x-external-user-id you used at registration time, or you will get an empty list.

Which events a subscription receives

The subscription was registered withIt receives
An Organization API key and x-external-user-idThe events of that end user's assets, in your organization
An Organization API key, no headerEvery event in your organization
A personal API keyThe events of your own assets

A subscription never receives another organization's events, even when the user id is the same.

The owner of an asset is the x-external-user-id it was uploaded with, or the API key's own user when no header was sent. So:

  • To follow one end user, register with the same x-external-user-id their uploads carry: a subscription registered for usr_dev_456 hears about the assets uploaded for usr_dev_456.
  • To follow everything, register without the header. An organization-wide subscription hears about every asset in the organization, whoever it was uploaded for.

An end user's event reaches both that user's subscriptions and the organization-wide ones. If you register both kinds for the same URL, that URL receives the event twice.

Endpoint

POST /v1/webhooks

Request Headers

Authorization: Bearer sk_live_your_key
x-external-user-id: usr_dev_456
Content-Type: application/json

Request Body

{
  "url": "https://api.yourdomain.com/agents/media-receiver",
  "events": ["asset.processed"],
  "config": {
    "include_embeddings": true,
    "include_text": true
  }
}

Response

{
  "id": 42,
  "url": "https://api.yourdomain.com/agents/media-receiver",
  "events": ["asset.processed"],
  "secret": "wh_sec_d83e1c9f824a73b9d0e1f2a3b4c5d6e7f8",
  "config": {
    "include_embeddings": true,
    "include_text": true
  }
}

[!IMPORTANT] Write down your generated signing secret when you receive the creation response. For security reasons, the secret is only returned in full upon registration and cannot be retrieved later in plaintext.


4. Cognitive Payload Structure

Below is an example of a fully enriched asset.processed event payload delivered to a client receiver when both include_embeddings and include_text are enabled:

{
  "event": "asset.processed",
  "timestamp": "2026-06-02T03:00:00.000Z",
  "data": {
    "assetId": "123e4567-e89b-12d3-a456-426614174000",
    "orgId": "org_default",
    "userId": "usr_dev_456",
    "originalName": "cambridge_meeting.jpg",
    "mimeType": "image/jpeg",
    "size": 2451092,
    "width": 4032,
    "height": 3024,
    "captureDate": "2026-05-24T18:45:00.000Z",
    "cameraMake": "Apple",
    "cameraModel": "iPhone 15 Pro",
    "status": "completed",
    "fileHash": "sha256_b2c4d6...",
    "url": "https://cdn.picsha.ai/usr_dev_456/123e4567-e89b-12d3-a456-426614174000/source.jpg",
    "tags": ["meeting", "indoors", "whiteboard", "laptop", "office"],
    "locationString": "Cambridge, MA, USA",
    "hlsStream": null,
    "aiAnalysis": {
      "summaryShort": "A team meeting in a well-lit office space with whiteboards.",
      "summaryLong": "A detailed shot of a collaborative workspace in Cambridge. There are three individuals seated around a conference table looking at a laptop. The background features a glass whiteboard covered with architectural diagrams and flowchart structures. Lighting is bright and even, indicating daytime office work.",
      "truncated": false
    },
    "rawExif": {
      "FocalLength": "6.86 mm",
      "FNumber": 1.78,
      "ExposureTime": "1/60",
      "ISO": 80
    },
    "embedding": [
      -0.012495,
      0.048291,
      -0.082491,
      0.001924,
      0.081726
      // ... 1024-float32 vector elements
    ],
    "createdAt": "2026-06-02T02:58:00.000Z",
    "updatedAt": "2026-06-02T02:59:30.000Z"
  }
}

5. Secure Signature Verification

To guarantee that incoming webhook payloads originate exclusively from Picsha AI, each tailored payload is cryptographically signed before dispatch using the webhook’s distinct secret.

Picsha attaches the following headers to each outgoing POST request:

  • X-Picsha-Event: The event type (e.g. asset.processed).
  • X-Picsha-Signature: The computed HMAC SHA-256 signature of the raw request body.

Node.js / TypeScript Verification Example

Below is a standard Express or Fastify middleware code snippet to securely verify the signature on your receiving backend:

import crypto from 'crypto';
import express from 'express';

const app = express();

function verifyPicshaSignature(
    secret: string,
    signature: string,
    rawBody: Buffer
): boolean {
    const computed = crypto
        .createHmac('sha256', secret)
        .update(rawBody)
        .digest('hex');

    return crypto.timingSafeEqual(
        Buffer.from(signature, 'hex'),
        Buffer.from(computed, 'hex')
    );
}

// Usage inside an Express Route:
// IMPORTANT: You must use express.raw() to capture the exact raw bytes sent by Picsha
app.post(
    '/webhooks/picsha', 
    express.raw({ type: 'application/json' }), 
    (req, res) => {
        const signature = req.headers['x-picsha-signature'] as string;
        const secret = process.env.PICSHA_WEBHOOK_SECRET!; // Your wh_sec_... key
        
        if (!signature) {
            return res.status(401).send('Missing signature header');
        }

        // Verify using the raw buffer, NOT a parsed JSON object
        const isValid = verifyPicshaSignature(secret, signature, req.body);
        if (!isValid) {
            return res.status(403).send('Invalid signature verification');
        }

        // Now that it's verified, we can safely parse the JSON payload
        const payload = JSON.parse(req.body.toString('utf8'));
        const { assetId, aiAnalysis, embedding } = payload.data;
        
        console.log(`Received secure update for asset: ${assetId}`);
        res.status(200).send({ received: true });
    }
);

Python Verification Example

Below is a standard Python / Flask implementation to verify the payload signature using the raw request bytes:

import hmac
import hashlib
import json
from flask import Flask, request, abort

app = Flask(__name__)

def verify_signature(secret: str, signature: str, raw_body: bytes) -> bool:
    computed = hmac.new(
        secret.encode('utf-8'),
        raw_body,
        hashlib.sha256
    ).hexdigest()
    
    return hmac.compare_digest(signature, computed)

@app.route('/webhooks/picsha', methods=['POST'])
def picsha_webhook():
    signature = request.headers.get('X-Picsha-Signature')
    secret = "wh_sec_your_key_here"
    
    if not signature:
        abort(401, description="Missing signature header")
        
    # Verify using the raw bytes, NOT the parsed JSON dictionary
    raw_body = request.get_data()
    if not verify_signature(secret, signature, raw_body):
        abort(403, description="Invalid cryptographic signature")
        
    # Signature is valid. Parse and hand off the data block directly to your AI agent!
    payload = request.get_json()
    data_block = payload.get('data', {})
    data = payload['data']
    print(f"Verified secure cognitive package for asset: {data['assetId']}")
    
    return {"status": "success"}, 200

6. Webhooks Feature Comparison

The table below contrasts Picsha AI's Enriched Agentic Webhooks with major competitors—Cloudinary, Uploadcare, and Bytescale:

Feature / Payload DataCloudinaryUploadcareBytescalePicsha AI (Agentic)
Technical Metadata (Size, Mime, Dims)YesYesYesYes
Basic Text Tags (e.g., ["car"])YesNoNoYes
HMAC Security SignaturesYesYesNoYes
Antivirus Scan StatusNoYesNoYes ($1.00 / 1k scans)
Claude Visual Summary (Visual Context)NoNoNoYes (Enriched)
Document OCR Text ExtractNoNoNoYes (Enriched)
A/V Speech-to-Text TranscriptsNoNoNoYes (Enriched)
Geocoded Location StringsNoNoNoYes (Enriched)
Multimodal Vector Embedding (1024d)NoNoNoYes (Enriched)

By combining cryptographically secure delivery, multi-tenant safety boundaries, and highly customized cognitive summaries, Picsha AI's Enriched Agentic Webhooks establish the premier standard for building high-performance, event-driven media intelligence systems.