A webhook is an HTTPS URL of yours that the API POSTs an event to when something happens: a file is done or failed, a project stops, or its processing service fails. Each delivery is signed, so your server can tell it came from SyncWords, and repeated until your server takes it. Polling GET /projects/{id}/files keeps working, and stays the way to catch up after your server was down for longer than deliveries are retried.

Create a webhook

The secret is shown only in this answer. Store it with your server’s other secrets; it is what you verify each delivery with. Lost it, or leaked it? Rotate it.
The URL must be https:// and reach a public address: private networks, loopback and link-local addresses are refused, when you create the webhook and again at every delivery, and so are host names without a dot and names under .local or .internal. An account has at most 10 webhooks, one per URL. A webhook receives the events that happen after it was created, for every project of the account.

Events

Every delivery is a JSON body of the same shape:
id is the event’s, the same on every attempt: keep the ids you have handled and drop a repeat. timestamp is when it happened. Events can arrive out of order; compare timestamp if the order matters to you. Payloads only grow. Fields may be added to data, and new event types added, without notice; none is removed or changes meaning. Ignore what you do not know. A change that would break that comes as a new event type (file.done.v2, say), sent beside the old one for as long as it takes to move.
file.done’s hash is the file’s processing key: the same for every run of that name in the project, retries and replacements included. Quote it to support; to tell runs apart, use completedAt, and to tell events apart, id. On file.done, download each output whose available is true with GET /projects/{id}/outputs/download?path=<path>. On file.failed, POST /projects/{id}/files/{name}/retry and start try it again. On project.error, POST /projects/{id}/start does. A project that reads a feed sends the same events. A file’s name is the one made from the feed item (4f1c2b9a7d3e.8a2b4c6d.mp4), and the files of an output feed are among its outputs, under feeds/<language>/. By the time file.done is sent, the output feed links the file, or will on its next rewrite, within 30 seconds. Such a project is never stopped for being idle, so it sends project.stopped with reason: "idle" only after its service has read failed for 2 hours.

Verify the signature

Each delivery carries three headers, as the Standard Webhooks specification has them: To verify one:
  1. Read the body as raw bytes, before any JSON parsing: re-serialised JSON does not match.
  2. Refuse a webhook-timestamp more than 5 minutes from your clock, so an old delivery cannot be replayed.
  3. Compute the signature and compare it, in constant time, with each space-separated entry of webhook-signature.
A receiver that verifies, drops repeats, and answers at once:
The Standard Webhooks libraries verify the same headers: pip install standardwebhooks, npm install standardwebhooks, and others for most languages.
Serve the receiver behind your usual HTTPS proxy or load balancer: the URL you register must be https://. POST /webhooks/{id}/test checks the whole path, signature included.

Rotate the secret

The answer carries the new secret, shown only there, and previousSecretExpiresAt. Until then every delivery is signed with both secrets, so:
  1. Rotate, and give your server the new secret beside the old one.
  2. Your server accepts a delivery when either secret’s signature matches (the receivers above already compare every entry of webhook-signature).
  3. Once the new secret is in place everywhere — within 24 hours — drop the old one.

Retries

A delivery is made when your server answers with a 2xx status within 10 seconds of the attempt starting: connecting, TLS and the status line all count, and the body you send back is not waited for. Anything else — another status, a redirect (redirects are not followed), a timeout, a refused connection — is tried again: A delivery still failing 24 hours after its event is given up, and shows status: "failed" in GET /webhooks/{id}/deliveries?status=failed. Each delivery there has lastStatus (your server’s last HTTP status, or null when it could not be reached) and lastError. Once your server is fixed, POST /webhooks/{id}/deliveries/{eventId}/retry sends one again, with another 24 hours of retries; deliveries are kept 7 days. After a longer outage, catch up by listing your projects’ files. Deliveries to one webhook go one at a time, and a few per webhook in each round: when your server does not answer, the rest of its deliveries wait for the next round rather than each waiting out its own timeout. Other accounts’ webhooks are not slowed by yours, nor yours by theirs. So that retries do no harm:
  • Answer fast, then do the work: queue the event and return 204.
  • Drop repeats by id: an attempt your server handled but answered too slowly is sent again.
  • Do not depend on order.

Limits