Webhooks
Configure Webhooks
Set notify_url in your Assembly Instructions, at the same level as steps. Once the Assembly
reaches a terminal state, Transloadit sends an HTTP POST to that URL.
Any status from 200 up to but not including
300 acknowledges delivery. Redirects and client or server errors are treated as failures. By default,
Transloadit retries failures 5 times with an exponential factor of 1.97.
Limit the Notification payload
By default, a Webhook includes the complete Assembly Status. Set notification_payload to an array
containing any combination of these supported filters:
without_params: the top-level raw Assembly instruction fieldsparams,template, andmerged_paramsare omitted.without_result_meta_data:metais omitted from each file inresults.without_results: the top-levelresultsobject is omitted.without_upload_meta_data:metais omitted from each file inuploads.without_uploads: the top-leveluploadsarray is omitted.
Notification replays reuse filters supplied in the original Assembly request. Filters defined only
in a Template are not preserved on replay, so a replay can include data omitted from the initial
Notification. Supply notification_payload in the original Assembly request when replays must
use the same filters.
Verify the signature
Assembly Webhooks use the application/x-www-form-urlencoded media type. The
transloadit field contains the exact serialized Assembly Status JSON, and the
signature field contains its lowercase hexadecimal HMAC.
To verify a Webhook:
- Read the
transloaditandsignatureform fields without modifying the payload string. - Calculate an
HMAC-SHA1hexadecimal digest over the exacttransloaditstring, using the trusted Auth Secret selected as described below. - Compare the calculated digest with
signatureusing a timing-safe comparison. - Parse
transloaditas JSON only after the signatures match.
An Assembly’s initial Notification uses the Auth Secret of the Auth Key that authenticated its
creation, including when it was created by an Assembly replay. Notification replays first look
up the Auth Key recorded in the Assembly Status as api_auth_key_id. If that key is not recorded,
cannot be resolved, has been deleted, or its lookup fails, the Notification replay uses the
authenticated replay caller’s Auth Secret instead.
Assembly replays retain the parent’s historical api_auth_key_id. For example, if Key A creates
an Assembly and Key B replays it, the new Assembly’s initial Notification is signed with B’s
secret. Replaying that Notification can use A’s secret, even when B calls both replay endpoints
and both keys remain active. Keep the applicable parent and replay-creation secrets available
to your verifier; do not assume every delivery for one Assembly uses the same secret.
Select verification secrets from trusted server-side configuration for the expected Workspace and Assembly, not from fields in the unverified payload. When more than one configured secret is applicable, accept the request only if its signature matches one of those trusted secrets. If none matches, reject the request; do not skip verification to accept a replay.
Unlike current API-request signatures, the Webhook signature is an unprefixed
sha1 digest for backwards compatibility. Treat the payload as untrusted and reject the request
when either field is absent, the signature is malformed, or the comparison fails.
Use one of our SDK verification helpers when available. If you implement verification yourself, do not reserialize the parsed JSON before calculating the HMAC: whitespace and object-key order are part of the signed byte sequence.
import { createHmac, timingSafeEqual } from 'node:crypto'
// authSecret must come from trusted server-side configuration.
function verifyTransloaditWebhook({ authSecret, payload, signature }) {
if (typeof payload !== 'string' || typeof signature !== 'string') return false
if (!/^[0-9a-f]+$/.test(signature)) return false
const expected = createHmac('sha1', authSecret).update(payload, 'utf8').digest()
if (signature.length !== expected.length * 2) return false
const received = Buffer.from(signature, 'hex')
return received.length === expected.length && timingSafeEqual(received, expected)
}