Skip to main content

Overview

Webhooks push events to your server in real-time. No polling needed.
Your App <-- Webhook <-- Waffo Pancake
When an event occurs (order completed, subscription activated, etc.), Waffo Pancake sends an HTTP POST request to your registered endpoint with the event data.

Setup

1

Create Endpoint

Build an HTTPS endpoint that accepts POST requests.
2

Register in Dashboard

3

Select Events

Choose which events to receive.
4

Save

Save your webhook configuration.

Event Types

Order Events

EventDescription
order.completedOrder completed successfully

Subscription Events

EventDescription
subscription.activatedSubscription activated
subscription.payment_succeededSubscription payment succeeded
subscription.updatedSubscription updated
subscription.cancelingSubscription cancellation requested
subscription.uncanceledSubscription reactivated
subscription.canceledSubscription canceled
subscription.past_dueSubscription payment past due

Refund Events

EventDescription
refund.succeededRefund processed successfully
refund.failedRefund processing failed

Payload Format

Webhook payloads follow the standard Waffo Pancake API response format:
{
  "event": "order.completed",
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "orderId": "660e8400-e29b-41d4-a716-446655440001",
    "amount": 2900,
    "currency": "USD",
    "status": "completed",
    "createdAt": "2026-01-15T10:30:00.000Z"
  }
}
All IDs are UUID v4 format. Amounts are in the smallest currency unit (e.g., 2900 = $29.00). Timestamps are ISO 8601 UTC.

Subscription Event Example

{
  "event": "subscription.activated",
  "data": {
    "id": "770e8400-e29b-41d4-a716-446655440002",
    "status": "active",
    "billingPeriod": "monthly",
    "amount": 2900,
    "currency": "USD",
    "currentPeriodEnd": "2026-02-15T10:30:00.000Z"
  }
}

Refund Event Example

{
  "event": "refund.succeeded",
  "data": {
    "id": "880e8400-e29b-41d4-a716-446655440003",
    "paymentId": "550e8400-e29b-41d4-a716-446655440000",
    "amount": 2900,
    "currency": "USD",
    "status": "succeeded",
    "createdAt": "2026-01-20T14:00:00.000Z"
  }
}

Respond Correctly

Return 200 OK quickly. Process asynchronously if your handler needs to do heavy work.
import { verifyWebhook, WebhookEventType } from "@waffo/pancake-ts";

export async function POST(req: Request) {
  const body = await req.text();
  const signature = req.headers.get("x-signature") ?? "";
  const timestamp = req.headers.get("x-timestamp") ?? "";

  // Built-in signature verification — no webhook secret needed
  const { event, data } = verifyWebhook({
    body,
    signature,
    timestamp,
    environment: "test", // or "prod"
  });

  switch (event) {
    case WebhookEventType.OrderCompleted:
      await handleOrderCompleted(data);
      break;
    case WebhookEventType.SubscriptionActivated:
      await handleSubscriptionActivated(data);
      break;
    case WebhookEventType.SubscriptionPaymentSucceeded:
      await handleSubscriptionPaymentSucceeded(data);
      break;
    case WebhookEventType.SubscriptionCanceling:
      await handleSubscriptionCanceling(data);
      break;
    case WebhookEventType.SubscriptionCanceled:
      await handleSubscriptionCanceled(data);
      break;
    case WebhookEventType.SubscriptionPastDue:
      await handleSubscriptionPastDue(data);
      break;
    case WebhookEventType.RefundSucceeded:
      await handleRefundSucceeded(data);
      break;
    case WebhookEventType.RefundFailed:
      await handleRefundFailed(data);
      break;
  }

  return new Response("OK", { status: 200 });
}
The TypeScript SDK’s verifyWebhook() function handles signature verification with embedded public keys for both test and production environments. No webhook secret configuration needed — just pass the request body, signature header, timestamp header, and environment.

Retry Policy

Failed deliveries retry automatically:
AttemptDelay
1Immediate
25 minutes
330 minutes
42 hours
524 hours
After 5 failures, the webhook is disabled. Re-enable in the Dashboard.

Idempotency

Events may be delivered more than once. Use the event data (such as the order or payment ID) for deduplication to ensure your handler processes each event only once.
const processedEvents = new Set();

app.post('/webhooks/waffo', (req, res) => {
  const { event, data } = req.body;
  const eventKey = `${event}:${data.id}`;

  if (processedEvents.has(eventKey)) {
    return res.status(200).send('Already processed');
  }

  processedEvents.add(eventKey);
  res.status(200).send('OK');

  // Process the event
  processWebhook(event, data);
});
For production, store processed event IDs in a database instead of in-memory to persist across server restarts.

Best Practices

  1. Use HTTPS — Required for production webhook endpoints
  2. Respond fast — Return 200 within 30 seconds; process heavy work asynchronously
  3. Handle duplicates — Events may be retried; use IDs for deduplication
  4. Process async — Do not block the response with heavy processing
  5. Log everything — Record incoming webhooks for debugging

Testing

Use Test Mode to send test events to your webhook endpoint:
  1. Switch to Test Mode in the Dashboard header
  2. Go to Developers —> Webhooks
  3. Perform actions (create orders, complete payments) in test mode
  4. Check your endpoint for incoming webhook events
Webhooks fire in both Test and Live modes. Make sure your endpoint can distinguish between test and live events using the X-Environment context of the original action.

Debugging

View webhook delivery logs in the Dashboard:
  • Delivery status (success/failed)
  • HTTP response code from your endpoint
  • Payload sent
  • Timestamps and retry attempts

Next Steps

Webhook Guide

Step-by-step webhook integration tutorial.

API Reference

Full API documentation.