> For the documentation index, fetch https://sync.so/docs/llms.txt. Append .md to a page URL for Markdown. Documentation-search MCP: https://sync.so/docs/_mcp/server.

# Webhooks

> Set up webhooks for real-time lip sync generation status updates. Includes signature verification, error handling, and code examples.

Sync Labs 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 endpoint that supports it.
2. Ensure your webhook endpoint can receive and handle HTTP `POST` requests and can consume the [Webhook Payload for status updates](/api-reference/api/webhooks-payload-reference/webhooks).

> **Note**
>
> 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.

If you are not receiving events, confirm that the exact `webhookUrl` was included in the generation request, the endpoint is publicly reachable over HTTPS, and it returns a `2xx` response to `POST` requests quickly. If delivery is missed because your endpoint is unavailable or rejects the request, poll the generation status endpoint as a fallback.

Generation webhook delivery is best-effort and is not automatically retried. Batch webhook delivery is retried until it is delivered, so batch consumers must safely handle duplicate notifications for the same batch ID.

## 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](/developer-guides/error-handling) 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 Labs, we sign each webhook request with a signature. The signature is included in the `Sync-Signature` header.

To verify signatures, use your [signing secret](https://sync.so/settings/webhooks). You can also retrieve it programmatically with [`GET /v2/organizations/webhook/secret`](/api-reference/api/organizations-api/get-webhook-secret). Store the full `whsec_...` value securely; `masked=true` is display-only and must not be used for verification.

The signature is made out of 2 components:

* Timestamp (at the time of sending the event)
* Signature hash (the timestamp and the exact raw JSON request body)

**`Javascript`**

```javascript Javascript
const express = require("express");
const { createHmac, timingSafeEqual } = require("crypto");

const app = express();

const WEBHOOK_SECRET = "whsec_";

// Keep the exact request bytes for signature verification.
app.use("/webhook", express.raw({ type: "application/json" }));

const verifySignature = (rawBody, signature, secret) => {
  try {
    if (!signature) {
      return false;
    }
    const [, timestamp, receivedSignature] =
      signature.match(/t=(\d+),v1=(.+)/) ?? [];
    if (!timestamp || !receivedSignature) {
      return false;
    }

    const signedPayload = Buffer.concat([
      Buffer.from(`${timestamp}.`),
      rawBody,
    ]);
    const expectedSignature = createHmac("sha256", secret)
      .update(signedPayload)
      .digest("hex");

    // Timing-safe comparison to prevent timing attacks
    return timingSafeEqual(
      Buffer.from(receivedSignature),
      Buffer.from(expectedSignature)
    );
  } catch (error) {
    return false;
  }
};

app.post("/webhook", (req, res) => {
  const signature = req.headers["sync-signature"];

  if (!verifySignature(req.body, signature, WEBHOOK_SECRET)) {
    return res.status(400).json({
      message: "Invalid signature",
    });
  }

  return res.status(200).json({
    message: "Webhook signature verified",
  });
});

app.listen(8080, () => {});
```

**`Python`**

```python Python
import hmac
import hashlib
import re
from flask import Flask, request, jsonify

app = Flask(__name__)

WEBHOOK_SECRET = 'whsec_'


def verify_signature(raw_body: bytes, signature: str, webhook_secret: str) -> bool:
    if not signature:
        return False

    try:
        match = re.match(r't=(\d+),v1=(.+)', signature)
        if not match:
            return False

        timestamp, received_signature = match.groups()
        if not timestamp or not received_signature:
            return False

        expected_signature = hmac.new(
            webhook_secret.encode("utf-8"),
            timestamp.encode("utf-8") + b"." + raw_body,
            hashlib.sha256
        ).hexdigest()

        # Timing-safe comparison to prevent timing attacks
        return hmac.compare_digest(received_signature, expected_signature)
    except Exception:
        return False


@app.route('/webhook', methods=['POST'])
def webhook():
    signature = request.headers.get('Sync-Signature')

    if not verify_signature(request.get_data(cache=True), signature, WEBHOOK_SECRET):
        return jsonify({'message': 'Invalid signature'}), 400

    return jsonify({'message': 'Webhook signature verified'}), 200


if __name__ == "__main__":
    app.run(host='0.0.0.0', port=8085)

```

## Frequently Asked Questions

#### How do I test webhooks locally?

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 Labs will send POST requests to your local server through the tunnel.

#### What if my endpoint is down?

For an individual generation, the notification may be lost if your webhook endpoint is unreachable when Sync Labs attempts delivery. There is no automatic retry for failed generation webhook deliveries. As a safety net, poll the generation status endpoint to check for completed or failed jobs that your webhook may have missed. Batch webhooks are retried until delivered, so deduplicate repeated notifications by batch ID.

#### How do I verify webhook signatures?

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.