Skip to main content

Overview

Webhooks let you receive real-time notifications when asset processing finishes. Instead of polling the submission status endpoint, your system is notified via HTTPS POST as soon as an event occurs.

How webhooks work

1

Create a subscription

Register an HTTPS endpoint URL and the event types you want to receive. The API returns a signing secret.
2

Receive deliveries

When an asset finishes processing, the API sends a signed POST to your endpoint.
3

Verify and acknowledge

Verify the signature, respond quickly with a 2xx, and process asynchronously.
4

Fetch detail

The payload does not embed results. Fetch the asset detail, issues, or topics from the REST API.

Event types

Only two event types exist.
Event typeTrigger
asset.processing.completedAn asset finishes processing successfully
asset.processing.failedAn asset fails to process

Creating a webhook subscription

POST /api/integrations/webhooks/subscriptions The request uses endpointUrl (HTTPS required), eventTypes, and an optional description.
{
  "endpointUrl": "https://example.com/webhooks",
  "eventTypes": ["asset.processing.completed", "asset.processing.failed"],
  "description": "Production webhook endpoint"
}
Response 201 Created. The secret is returned only on create:
{
  "id": "550e8400-...",
  "workspaceId": "660e8400-...",
  "endpointUrl": "https://example.com/webhooks",
  "eventTypes": ["asset.processing.completed", "asset.processing.failed"],
  "description": "Production webhook endpoint",
  "enabled": true,
  "secret": "whsec_AbCdEf...",
  "createdAt": "2026-06-03T12:00:00Z",
  "updatedAt": "2026-06-03T12:00:00Z"
}
Save the secret immediately — it is never returned again after create. You use it to verify webhook signatures.
A 422 is returned for a non-HTTPS URL or when the workspace reaches its subscription limit.

Webhook delivery payload

When an event occurs, the API sends a Standard Webhooks envelope to your endpoint. The outer fields are id, type, timestamp, and data. The data object is snake_case.
{
  "id": "evt_uuid",
  "type": "asset.processing.completed",
  "timestamp": "2026-06-03T12:05:00Z",
  "data": {
    "asset_id": "a1...",
    "submission_id": "s1...",
    "workspace_id": "w1...",
    "status": "completed",
    "filename": "campaign-video.mp4",
    "issue_count": 3
  }
}
A failure delivery uses the same envelope with data.status set to "failed" and an added data.error_message:
{
  "id": "evt_uuid",
  "type": "asset.processing.failed",
  "timestamp": "2026-06-03T12:10:00Z",
  "data": {
    "asset_id": "a1...",
    "submission_id": "s1...",
    "workspace_id": "w1...",
    "status": "failed",
    "error_message": "Source media could not be decoded.",
    "filename": "bad.mp4"
  }
}
The payload does not embed results. To get issues, topics, or full asset detail, fetch them from the REST API: GET /api/integrations/submissions/{submission_id}/assets/{asset_id}, its /issues, or its /topics. See Assets.

Verifying webhook signatures

Deliveries are signed using the Standard Webhooks format. Always verify the signature using your subscription secret. After verification, read the event type from event["type"].
from standardwebhooks.webhooks import Webhook

wh = Webhook("whsec_AbCdEf...")

try:
    event = wh.verify(
        payload=request.body,
        headers=dict(request.headers),
    )
    print("Signature valid:", event["type"])
except Exception as e:
    print("Invalid signature:", e)
    return {"error": "Unauthorized"}, 401

Handling webhooks

Your endpoint should:
  1. Verify the signature (see above).
  2. Respond quickly — return a 2xx immediately, then process in the background.
  3. Process idempotently — handle the same event id being delivered more than once.
  4. Fetch detail — load issues or topics from the REST API as needed.
from fastapi import FastAPI, Request
from standardwebhooks.webhooks import Webhook

app = FastAPI()
wh = Webhook("whsec_AbCdEf...")

@app.post("/webhooks")
async def handle_webhook(request: Request):
    try:
        payload = await request.body()
        event = wh.verify(payload, dict(request.headers))
    except Exception:
        return {"error": "Unauthorized"}, 401

    event_id = event["id"]
    event_type = event["type"]
    print(f"Received {event_type} (ID: {event_id})")

    # Queue for background processing; fetch asset detail from the REST API.
    return {"status": "received"}

Delivery and retries

Webhook delivery is handled by a delivery provider. If your endpoint returns a non-2xx status, the delivery is retried automatically. Review attempt history through the deliveries endpoint or in the control plane rather than relying on a fixed retry schedule. GET /api/integrations/webhooks/subscriptions/{subscription_id}/deliveries Query parameters: status (optional filter) and limit (1–200, default 50).

Testing webhooks

Using ngrok

For local testing, expose your local server publicly:
ngrok http 3000
This gives you a public HTTPS URL like https://abc123.ngrok.io. Create a subscription pointing to it.

Test endpoint

Use the health check webhook test endpoint to validate signature verification without affecting production data:
POST /health/webhook-test

Managing subscriptions

List subscriptions

GET /api/integrations/webhooks/subscriptions Returns a paginated envelope. The secret is never included after create.
{
  "items": [
    {
      "id": "550e8400-...",
      "workspaceId": "660e8400-...",
      "endpointUrl": "https://example.com/webhooks",
      "eventTypes": ["asset.processing.completed", "asset.processing.failed"],
      "description": "Production webhook endpoint",
      "enabled": true,
      "createdAt": "2026-06-03T12:00:00Z",
      "updatedAt": "2026-06-03T12:00:00Z"
    }
  ],
  "page": 1,
  "pageSize": 10,
  "total": 3,
  "totalPages": 1
}
Query parameters: page (≥1), pageSize (1–100), sortBy (endpoint_url | created_at | updated_at), sortOrder (asc | desc), enabled (bool), and search.

Get a subscription

GET /api/integrations/webhooks/subscriptions/{subscription_id} Returns a single subscription (without secret).

Update a subscription

PATCH /api/integrations/webhooks/subscriptions/{subscription_id} Only enabling or disabling is supported:
{ "enabled": false }

Delete a subscription

DELETE /api/integrations/webhooks/subscriptions/{subscription_id} Returns 204 No Content.

Webhook best practices

  • Verify signatures — always validate the signature using your subscription secret.
  • Respond quickly — return a 2xx promptly and process asynchronously.
  • Process idempotently — handle duplicate event IDs gracefully.
  • Fetch detail on demand — the payload does not embed results; load them from the REST API.
  • Store the secret securely — keep it in environment variables or a secret manager.