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.status_changed", description: "Sync status updates" },
    { event: "product.updated", description: "Sync catalog changes" },
    { event: "app.installed", description: "Provision merchant resources" },
    { event: "app.uninstalled", description: "Clean up merchant data" },
  ],
  // ... 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

All webhook payloads share the same envelope:

{
  "id": "evt_01HX4KQMZ7B9XVNR2T6WQFG3P",
  "event": "order.created",
  "store": "my-store",
  "timestamp": "2024-03-01T14:30:00Z",
  "data": {
    // Event-specific data
  }
}

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 minute
2nd retry5 minutes
3rd retry30 minutes
4th retry2 hours

After 4 failed retries, the delivery is abandoned and logged.

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) {
  const orderId = payload.data.id;
  const deliveryId = payload.id; // 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