Webhooks

Webhook endpoint implementation details

This guide will walk you through setting up and securely handling webhooks. The implementation follows the Standard Webhooks specification.

Webhook Delivery

Timeouts

  • Webhooks have a 10-second timeout window, including both connection and read timeouts.
  • If a webhook delivery attempt fails, we will retry sending the event using exponential backoff to avoid overloading your system.

Retry Schedule The retry attempts follow this schedule:

  • Immediately (first retry)
  • 5 seconds after the first failure
  • 5 minutes after the second failure
  • 30 minutes after the third failure
  • 2 hours after the fourth failure
  • 5 hours after the fifth failure
  • 10 hours after the sixth failure
  • 14 hours after the seventh failure
  • 20 hours after the eighth failure
  • 24 hours after the ninth failure (final retry)

Total Timeline

For example, if a webhook fails three times before succeeding, it will be delivered roughly 35 minutes and 5 seconds following the first attempt.

If all the retries are exhausted, the related webhook endpoint will be disabled and a notification to the owner of the organization will be triggered.


If a webhook endpoint is removed or disabled, all delivery attempts to that endpoint will be stopped immediately.

Idempotency

  • Each webhook event contains a unique webhook-id header. Use this to implement idempotency and avoid processing the same event multiple times.
  • Even if you receive the same event more than once (due to retries), your system should handle it gracefully without causing errors or duplicate actions.

Ordering

  • Webhook delivery order is not guaranteed, as webhooks may be delivered out of order due to retries or network issues.
  • Ensure your system can handle events arriving out of order by using the webhook-id header to process events correctly.

Payload

The following will be payload format:

{
  "type": "video.finished",
  "org_id": "3921aa39-ffff-ffff-aa80-b93e78765a29",
  "data": {
    "id": "4b65cc44-ffff-ffff-a7a2-f3ae21afe43c",
    "self": "https://api.animationapi.com/v1/videos/4b65cc44-ffff-ffff-a7a2-f3ae21afe43c",
    "status": "COMPLETED",
    "video_url": "https://cdn.animationapi.com/3921aa39-ffff-ffff-ffff-b93e78765a29/vid_A8pfFffFFf4LkDUl-7Dqp.gif",
    "created_at": "2025-10-18T17:20:38.835Z",
    "updated_at": "2025-10-18T17:21:06.721Z"
  }
}

Securing Webhooks

To ensure the security of your webhooks, always validate the payloads and use HTTPS.

Verifying Signatures

Each webhook request includes a webhook-signature header — an HMAC SHA256 signature of the webhook payload and timestamp, signed with your secret key. To verify a webhook came from AnimationAPI:

  1. Compute the HMAC SHA256 of this string using your webhook secret key obtained from the Create Webhook response
  2. Concatenate the webhook-id, webhook-timestamp, and stringified payload values from the webhook with periods (.) The respective payloads for outgoing webhooks can be found in the Webhook Payload (Video Generation Started, Video Generation Finished, Video Generation Failed).
  3. Compare the computed signature to the received webhook-signature header value. If they match, the webhook is authentic.

Since we follow the Standard Webhooks specification, you can use one of their libraries to verify the signature: https://github.com/standard-webhooks/standard-webhooks/tree/main/libraries

Responding to Webhooks

  • Your webhook handler must return a 2xx status code to acknowledge receipt of the event.
  • Any other response will be treated as a failure, and the webhook will be retried.

Example code

// index.ts in express
import { Webhook } from "standardwebhooks";
import { type Request, type Response } from "express";

const webhook = new Webhook("ANIMATION_API_WEBHOOK_SECRET");

// Add this endpoint using Create Webhook request
app.post("/webhook/animationapi", async (req: Request, res: Response) => {
  try {
    const body = req.body;

    const webhookHeaders: WebhookUnbrandedRequiredHeaders = {
      "webhook-id": (req.headers["webhook-id"] || "") as string,
      "webhook-signature": (req.headers["webhook-signature"] || "") as string,
      "webhook-timestamp": (req.headers["webhook-timestamp"] || "") as string,
    };

    const raw = JSON.stringify(body);

    const payload = await webhook.verify(raw, webhookHeaders);

    // ... Rest of your code HERE ...

    res.status(200).json({ received: true });
  } catch (error) {
    console.error("Error processing webhook:", error);
    res.status(400).json({ error: "Webhook handler failed" });
  }
});