Skip to content

Webhooks

Register webhook subscriptions for your tenant and what ships today.

5 min read

What ships today

Webhook subscriptions and signed delivery ship as one feature: a deployment either has delivery configured or it does not. On ReplayCore Cloud it is configured, so a subscription you create starts receiving signed deliveries. Where delivery is not configured, for example a self-hosted deployment without an integration secrets key, POST /v1/webhooks returns 503 with the code WEBHOOK_DELIVERY_UNCONFIGURED rather than accepting a subscription it could never deliver to. If you get that response, no subscription was created and there is nothing waiting in a queue for you.

Subscriptions are managed under /v1/webhooks (POST to create, GET to list, DELETE /v1/webhooks/{id} to remove), authenticated with ReplayCore request signing.

Creating a subscription

POST /v1/webhooks with a JSON body containing the endpoint url and the list of events you want. Validation rules: the URL must be HTTPS, at most 2048 characters, and must not point at a private, loopback, or otherwise reserved address (requests to internal ranges are rejected at creation as SSRF protection). The events list must contain between 1 and 32 unique event names, and every name must be one of the eight below. An unrecognised name is rejected with 422 VALIDATION_ERROR listing the valid set, rather than accepted into a subscription that could never fire.

A successful create returns 201 with a signing secret in the response body. That is the only time the secret is ever shown: listing your subscriptions never returns it again, so store it when you receive it.

POST /v1/webhooks body
{
  "url": "https://hooks.example.com/replaycore",
  "events": ["collection.finalized", "asset.ready"]
}

Events you can subscribe to

These eight names are the complete vocabulary. A collection is a recorded scope; an asset is a playable artefact within one.

collection.finalized fires when the collection's scope has concluded and its recorded state is durable. This is the event to use if you want to know that a recording finished. It fires whether or not any asset has been released yet.

collection.released fires when the collection became released, either by an automatic policy trigger or an explicit operator action. collection.revoked fires when the collection was permanently taken down.

asset.processing fires when the media pipeline began producing playable bytes. asset.ready fires when playable bytes exist. Note that a ready asset may still be held, so ready means playable, not necessarily publicly visible.

asset.failed fires when the media pipeline failed permanently. asset.expired fires when the asset was reaped by retention. asset.deleted fires when the asset was deleted by an operator action or redaction, rather than by the normal retention path.

Verifying a delivery

Each delivery is a POST carrying X-FV-Event (the event name), X-FV-Event-Id, X-FV-Timestamp and X-FV-Signature. The signature is 'sha256=' followed by the hex HMAC-SHA256 of the string '<timestamp>.<raw body>', keyed with your subscription secret. Compute it over the raw body bytes before any JSON parsing, and compare in constant time.

Deliveries are logged and a failed delivery can be replayed, so treat your endpoint as idempotent on event_id: receiving the same event id twice is expected and must not double-apply.

To retry a delivery your endpoint dropped, call POST /v1/webhooks/deliveries/{id}/replay with an rc_live_ key carrying the collections:write scope. Every call is a genuine new attempt and increments the retry count, so there is no idempotency key on it. A delivery that already succeeded returns 409 DELIVERY_NOT_REPLAYABLE.

If delivery is not enabled for you

Pair the panel with a Discord channel webhook (see the Discord integration article) or poll the Replays API from your own tooling at a modest interval.