Webhooks

Webhooks Overview

Understand how Whatalo delivers real-time event notifications to your plugin and how to declare webhook subscriptions in your manifest.

Webhooks notify your plugin of events as they happen in a merchant's store. When a new order is placed, a product is updated, or a customer registers, Whatalo sends an HTTP POST request to your webhookUrl with a structured JSON payload.

How It Works

  1. Declare events in your whatalo.app.ts manifest
  2. Set your webhookUrl — Whatalo sends all events here
  3. Verify signatures on every incoming request
  4. Process the event payload and respond with 200 OK within 15 seconds

Manifest Declaration

// whatalo.app.ts
import { defineApp } from "@whatalo/plugin-sdk";

export default defineApp({
  name: "My Plugin",
  pluginId: "my-plugin",
  webhookUrl: "https://my-plugin.com/api/webhooks",
  webhooks: [
    { event: "order.created", description: "Track new orders for fulfilment" },
    { event: "order.updated", description: "Sync order changes" },
    { event: "product.updated", description: "Sync catalog changes" },
    { event: "checkout.completed", description: "React to completed checkouts" },
  ],
  // ... rest of manifest
});

Only declare the events your plugin actually handles. Declaring unused events creates unnecessary load and confuses merchants reviewing your plugin's permissions.

HTTP Delivery Details

Every webhook delivery is a POST request with these characteristics:

PropertyValue
MethodPOST
Content-Typeapplication/json
User-AgentWhatalo-Webhooks/1.0
Timeout15 seconds

Request Headers

HeaderDescription
X-Webhook-IdUnique delivery identifier — use for idempotency checks
X-Webhook-EventEvent type (e.g., order.created)
X-Webhook-SignatureHMAC-SHA256 signature for verification
X-Webhook-TimestampUnix timestamp of when the signature was generated

Payload Structure

Webhook payload bodies contain event-specific data. The event name is delivered in X-Webhook-Event, not in the JSON body:

{
  "event_id": "ord_abc123",
  "occurred_at": "2026-03-01T14:30:00.000Z",
  "order": {
    "id": "ord_abc123",
    "status": "pending"
  },
  "store": {
    "id": "sto_abc123",
    "name": "My Store",
    "timezone": "America/Santo_Domingo"
  }
}

Response Requirements

Your endpoint must respond with HTTP 200 OK within 15 seconds. Any other status code or a timeout is treated as a delivery failure.

Whatalo retries failed deliveries with exponential backoff:

AttemptDelay
1st retry1 second
2nd retry2 seconds

Failed deliveries are attempted up to 3 times total. The configured delay list is 1s, 2s, 4s, but only the 1s and 2s delays occur between the 3 attempts.

Idempotency

Because deliveries can be retried, your handler must be idempotent — processing the same event twice must not produce duplicate side effects.

async function handleOrderCreated(payload: WebhookPayload, context: WebhookHandlerContext) {
  const orderId = payload.order.id;
  const deliveryId = context.deliveryId; // X-Webhook-Id value

  // Check if this delivery has already been processed
  const existing = await db.processedEvents.findUnique({
    where: { deliveryId },
  });

  if (existing) {
    return; // Already processed — skip
  }

  // Process the order
  await fulfilOrder(orderId);

  // Record that this delivery was processed
  await db.processedEvents.create({ data: { deliveryId } });
}

Next Steps

On this page