Webhooks
Examples

Webhook Examples

This page provides complete, production-ready examples for receiving and processing Ignite webhooks in different programming languages.

Complete Server Examples

// pages/api/webhook.ts
import { createHmac, timingSafeEqual } from "crypto";
import { NextApiRequest, NextApiResponse } from "next";
 
// Disable body parser to access raw body for signature verification
export const config = {
  api: {
    bodyParser: false,
  },
};
 
interface VideoWebhookData {
  id: string;
  status: "COMPLETE" | "PROCESSING" | "NO_FILE" | "FAILED";
  title: string;
  src?: {
    thumbnailUrl?: string;
    mp4?: Array<{ name: string; url: string; width: number; height: number }>;
  };
  // ... other video properties
}
 
async function getRawBody(req: NextApiRequest): Promise<string> {
  const chunks: Buffer[] = [];
  for await (const chunk of req) {
    chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
  }
  return Buffer.concat(chunks).toString("utf-8");
}
 
function isValidSignature(
  rawBody: string,
  signatureHeader: string | string[] | undefined,
  secret: string
): boolean {
  try {
    if (typeof signatureHeader !== "string") {
      return false;
    }
 
    const time = signatureHeader.match(/t=(\d+)/)?.[1];
    const token = signatureHeader.match(/v1=(\w+)/)?.[1];
 
    if (!time || !token) {
      return false;
    }
 
    const signedPayload = `${time}.${rawBody}`;
    const hmac = createHmac("sha256", secret);
    const calculatedSignature = hmac.update(signedPayload).digest("hex");
 
    return timingSafeEqual(
      Buffer.from(token),
      Buffer.from(calculatedSignature)
    );
  } catch {
    return false;
  }
}
 
export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  if (req.method !== "POST") {
    return res.status(405).end();
  }
 
  const rawBody = await getRawBody(req);
 
  if (
    !isValidSignature(
      rawBody,
      req.headers["x-webhook-signature"],
      process.env.WEBHOOK_SIGNING_SECRET!
    )
  ) {
    console.error("Invalid webhook signature");
    return res.status(403).end();
  }
 
  const eventType = req.headers["x-webhook-event"];
  const deliveryId = req.headers["x-webhook-delivery-id"];
  const video = JSON.parse(rawBody) as VideoWebhookData;
 
  // Process based on event type
  switch (eventType) {
    case "video.created":
      console.log("New video created:", video.id, video.title);
      // await handleVideoCreated(video);
      break;
 
    case "video.updated":
      console.log("Video updated:", video.id, video.title);
      if (video.status === "COMPLETE") {
        // Video processing finished - update your database
        // await db.updateVideo(video.id, {
        //   thumbnail: video.src?.thumbnailUrl,
        //   sizes: video.src?.mp4,
        // });
      }
      break;
 
    case "video.deleted":
      console.log("Video deleted:", video.id);
      // await handleVideoDeleted(video);
      break;
  }
 
  return res.status(204).end();
}

Webhook Deliveries

Every webhook delivery attempt is logged. If a delivery fails, the system will automatically retry.

Delivery Status

StatusDescription
successEvent was successfully delivered (HTTP 2xx response)
failedDelivery failed (timeout, HTTP 4xx/5xx, network error)

Delivery Information

Each delivery record contains:

FieldDescription
responseCodeHTTP status code returned by your endpoint
responseBodyResponse body from your endpoint (truncated for large responses)
errorMessageError message if delivery failed
deliveredAtTimestamp of the delivery attempt
attemptNumberWhich attempt this was (increments with retries)
nextRetryAtWhen the next retry will be attempted (if applicable)

Retry Behavior

  • Failed deliveries are automatically retried
  • If nextRetryAt is set, a retry is scheduled
  • If nextRetryAt is not set, no more retries will be attempted

Best Practices

Following these best practices ensures reliable webhook processing and helps with debugging issues.

Response Time

Your endpoint must respond within 30 seconds. For long-running operations, acknowledge the webhook immediately and process asynchronously:

app.post('/webhook', async (req, res) => {
  // Immediately acknowledge receipt
  res.status(200).json({ received: true });
  
  // Process asynchronously (don't await)
  processWebhookAsync(req.body).catch(console.error);
});
 
async function processWebhookAsync(video) {
  // Long-running operations here
}

Security

  • Keep secrets secure — Never expose your signing secret in client-side code or version control
  • Use HTTPS — Always use HTTPS endpoints in production
  • Validate everything — Always verify signatures before processing
  • Rate limiting — Consider implementing rate limiting on your endpoint

Reliability

  • Idempotency — Store and check the X-Webhook-Delivery-Id to handle duplicate deliveries
  • Logging — Log all webhook requests for debugging
  • Monitoring — Set up alerts for failed webhook processing
  • Error handling — Return appropriate HTTP status codes so retries work correctly

HTTP Status Codes

CodeMeaning
200-299Success — event processed
4xxClient error — will be retried
5xxServer error — will be retried

Troubleshooting

No Events Received

  1. Check if the webhook subscription is set to Active
  2. Verify the correct event types are selected
  3. Ensure events are happening in the correct workspace
  4. Check your server logs for incoming requests

Signature Verification Fails

  1. Verify you're using the correct signing secret (not an API token)
  2. Ensure the request body is not modified before verification
  3. Check JSON serialization matches (compact, no extra whitespace)
  4. Verify the signature header format is parsed correctly: t=timestamp,v1=signature

Timeout Errors

  1. Respond within 30 seconds
  2. Move long operations to background processing
  3. Return 200 immediately, process asynchronously

Duplicate Events

  1. Implement idempotency using X-Webhook-Delivery-Id
  2. Check attemptNumber for retries
  3. Store processed delivery IDs in a database