Webhooks

Sync webhooks deliver real-time HTTP POST notifications when your lip sync generation jobs complete or fail. Use webhooks instead of polling to build efficient, event-driven workflows that respond to generation status changes without repeated API calls.

How do I set up webhooks?

To utilize webhooks:

  1. Specify a webhookUrl parameter when calling an API endpoints that support it.
  2. Ensure your webhook endpoint can receive and handle HTTP POST requests and can consume the Webhook Payload for status updates.

For security purposes, your webhook URL must be configured to accept and process HTTPS POST requests.

By leveraging webhooks, you can streamline your workflow with non-blocking calls, allowing you to focus on other tasks while receiving job completion notifications.

Error Handling in Webhooks

When a generation fails, the webhook payload will include error information in two fields:

  • error: A human-readable error message describing what went wrong
  • error_code: A specific error code that can be used for programmatic error handling (see the Error Handling guide for a complete list of error codes)

This improved error structure allows you to both display meaningful messages to users and implement specific error handling logic based on the error codes.

Verify webhook signatures

To ensure webhook requests are coming from Sync, we sign each webhook request with a signature. The signature is included in the Sync-Signature header.

To verify signatures, use your signing secret.

The signature is made out of 2 components:

  • Timestamp (at the time of sending the event)
  • Signature hash (timestamp and the JSON payload)
1const express = require("express");
2const { createHmac, timingSafeEqual } = require("crypto");
3
4const app = express();
5
6const WEBHOOK_SECRET = "whsec_";
7
8app.use(express.json());
9
10const verifySignature = (payload, signature, secret) => {
11 try {
12 if (!signature) {
13 return false;
14 }
15 const [, timestamp, receivedSignature] =
16 signature.match(/t=(\d+),v1=(.+)/) ?? [];
17 if (!timestamp || !receivedSignature) {
18 return false;
19 }
20
21 const expectedSignature = createHmac("sha256", secret)
22 .update(`${timestamp}.${JSON.stringify(payload)}`)
23 .digest("hex");
24
25 // Timing-safe comparison to prevent timing attacks
26 return timingSafeEqual(
27 Buffer.from(receivedSignature),
28 Buffer.from(expectedSignature)
29 );
30 } catch (error) {
31 return false;
32 }
33};
34
35app.post("/webhook", (req, res) => {
36 const signature = req.headers["sync-signature"];
37
38 if (!verifySignature(req.body, signature, WEBHOOK_SECRET)) {
39 return res.status(400).json({
40 message: "Invalid signature",
41 });
42 }
43
44 return res.status(200).json({
45 message: "Webhook signature verified",
46 });
47});
48
49app.listen(8080, () => {});
  • Batch Processing — use webhooks with batch processing for high-volume workflows
  • Rate Limits — understand API rate limiting and how it affects webhook delivery

Frequently Asked Questions

Use a tunneling tool like ngrok to expose your local server to the internet. Start your webhook endpoint locally, then run ngrok to get a public HTTPS URL. Pass that URL as the webhookUrl when creating a generation. Sync will send POST requests to your local server through the tunnel.

If your webhook endpoint is unreachable when Sync attempts delivery, the notification may be lost. There is no automatic retry for failed webhook deliveries. As a safety net, you can poll the generation status endpoint to check for completed or failed jobs that your webhook may have missed.

Each webhook request includes a Sync-Signature header containing a timestamp and HMAC-SHA256 hash. Use your signing secret from the webhooks settings page to compute the expected signature and compare it using a timing-safe comparison function to prevent timing attacks. See the code examples above.