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.

[!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 Picsha already stores, 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_.

[!NOTE] Webhook subscriptions are keyed on the pair of your organization and the x-external-user-id you register with (defaulting to system when omitted). GET /v1/webhooks, PATCH, and DELETE only ever see subscriptions registered under the same pair — send the same x-external-user-id you used at registration time, or you will get an empty list.

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.