Frame.io · event: file.ready ·
content type: application/json
This is a representative sample — fake IDs, real structure. To see a payload with your data, point the provider at a capture URL (button below).
One click creates a free capture bin with this exact payload already in it — pretty-printed, headers inspectable, replayable to your own endpoint:
Then paste the bin's URL into Frame.io's webhook settings to capture a real delivery. No signup.
content-type: application/json user-agent: Frame.io V4 API x-frameio-request-timestamp: 1788513480 x-frameio-signature: v0=965894a45763bf29ac6d5df208b897820f0650d43a1c183ddbdb18569fc0e541
{
"account": {
"id": "6f70f1bd-7e89-4a7e-b4d3-7e576585a181"
},
"project": {
"id": "7e46e495-4444-4555-8649-bee4d391a997"
},
"resource": {
"id": "d3075547-4e64-45f0-ad12-d075660eddd2",
"type": "file"
},
"type": "file.ready",
"user": {
"id": "56556a3f-859f-4b38-b6c6-e8625b5da8a5"
},
"workspace": {
"id": "378fcbf7-6f88-4224-8139-6a743ed940b2"
}
}
hookden-frameio-sample-signing-secret into the
signature debugger (Frame.io scheme), or set
the Frame.io scheme on a capture bin for live ✓/✗ badges. It's
v0= + hex HMAC-SHA256 over v0:timestamp:rawBody —
Slack's exact wire format under Frame.io headers. The signing secret is
non-configurable and returned only in the create-webhook response
(V4: POST /v4/accounts/…/workspaces/…/webhooks) — lose
it and you recreate the webhook.int(curr_time) - int(req_time) < 500, i.e. 8m20s (and only
in the future-to-past direction). The sample also encodes with
latin-1 and compares with == — if you port it, use
hmac.compare_digest and pick one window on purpose.resource carries
only an id and type — no file name, no status detail.
account / workspace / project /
user ids are included precisely so you can route and filter without an
API call; anything more means a GET on the resource (V4 auth =
Adobe-Console OAuth tokens only — legacy developer tokens are rejected).file.created fires before the upload finishes.
The docs say so explicitly — the record exists, the bytes may not.
file.ready (this event) = all transcodes complete;
file.upload.completed = bytes uploaded. Wiring "download the file" to
file.created is the classic bug in Frame.io integrations.account.id added to payloads, and legacy asset.* events
split into file.* / folder.*. The user-agent
tells you which API hit you: Frame.io V4 API vs
Frame.io Legacy API — and V2 endpoints are removed on
December 1, 2026, so unmigrated legacy webhooks die with them.Reproduce this delivery against any endpoint (your handler, a bin, staging):
$ curl -X POST https://your-endpoint.example/hook \
-H 'content-type: application/json' \
-H 'user-agent: Frame.io V4 API' \
-H 'x-frameio-request-timestamp: 1788513480' \
-H 'x-frameio-signature: v0=965894a45763bf29ac6d5df208b897820f0650d43a1c183ddbdb18569fc0e541' \
-d '{
"account": {
"id": "6f70f1bd-7e89-4a7e-b4d3-7e576585a181"
},
"project": {
"id": "7e46e495-4444-4555-8649-bee4d391a997"
},
"resource": {
"id": "d3075547-4e64-45f0-ad12-d075660eddd2",
"type": "file"
},
"type": "file.ready",
"user": {
"id": "56556a3f-859f-4b38-b6c6-e8625b5da8a5"
},
"workspace": {
"id": "378fcbf7-6f88-4224-8139-6a743ed940b2"
}
}'