Skip to main content
Crave sends webhook events to your server when key actions occur — such as an order being placed, confirmed, or completed. Use webhooks to trigger fulfillment workflows, send notifications, or sync data with external systems.

How webhooks work

1

Configure a webhook URL

Register your endpoint in the Crave Dashboard under Settings > Webhooks.
2

Crave sends a POST request

When an event occurs, Crave sends a JSON payload to your URL.
3

Your server processes the event

Parse the payload, verify the signature, and execute your business logic.
4

Return a 200 response

Acknowledge receipt within 10 seconds. Crave retries on failure.

Event types

EventTrigger
order.createdPayment succeeds and order is created
order.confirmedRestaurant confirms the order
order.readyOrder is ready for pickup or delivery
order.completedOrder is fulfilled
order.cancelledOrder is cancelled by restaurant or customer
cart.abandonedCart has been inactive for the configured timeout

Payload format

Every webhook delivery sends a POST request with a JSON body:
{
  "id": "evt_abc123",
  "type": "order.created",
  "createdAt": "2025-03-15T18:30:00Z",
  "data": {
    "orderId": "ord_456",
    "cartId": "cart_789",
    "locationId": "loc_123",
    "customerName": "Alex Johnson",
    "customerEmail": "alex@example.com",
    "total": "27.98",
    "currency": "usd",
    "fulfillmentMethod": "delivery",
    "items": [
      {
        "name": "Margherita Pizza",
        "quantity": 2,
        "total": "25.98"
      }
    ]
  }
}

Verify webhook signatures

Every webhook includes a X-Crave-Signature header containing an HMAC-SHA256 signature. Verify it to ensure the request came from Crave.
import { createHmac } from 'crypto';

const WEBHOOK_SECRET = process.env.CRAVEUP_WEBHOOK_SECRET!;

export async function POST(request: Request) {
  const body = await request.text();
  const signature = request.headers.get('X-Crave-Signature');

  const expected = createHmac('sha256', WEBHOOK_SECRET)
    .update(body)
    .digest('hex');

  if (signature !== expected) {
    return new Response('Invalid signature', { status: 401 });
  }

  const event = JSON.parse(body);

  switch (event.type) {
    case 'order.created':
      await handleOrderCreated(event.data);
      break;
    case 'order.completed':
      await handleOrderCompleted(event.data);
      break;
  }

  return new Response('OK', { status: 200 });
}
Always verify the signature before processing the event. Without verification, an attacker could send fake events to your endpoint.

Retry policy

If your endpoint returns a non-2xx status or times out (10 seconds), Crave retries with exponential backoff:
AttemptDelay
1st retry30 seconds
2nd retry2 minutes
3rd retry15 minutes
4th retry1 hour
5th retry4 hours
After 5 failed attempts, the event is marked as failed. You can manually retry failed events from the Dashboard.

Idempotency

Webhook deliveries may arrive more than once. Use the id field to deduplicate:
async function handleOrderCreated(data: OrderData) {
  // Check if we already processed this event
  const existing = await db.processedEvents.findUnique({
    where: { eventId: data.orderId },
  });

  if (existing) return; // already processed

  await db.processedEvents.create({
    data: { eventId: data.orderId },
  });

  // Process the order...
}

Common use cases

Send an order confirmation email:
case 'order.created':
  await sendEmail({
    to: event.data.customerEmail,
    subject: `Order confirmed — ${event.data.locationName}`,
    body: `Your order of ${event.data.items.length} items is being prepared.`,
  });
  break;
Sync with a POS system:
case 'order.created':
  await posClient.createOrder({
    externalId: event.data.orderId,
    items: event.data.items,
    total: event.data.total,
  });
  break;
Track abandoned carts:
case 'cart.abandoned':
  await analytics.track('cart_abandoned', {
    cartId: event.data.cartId,
    total: event.data.total,
    itemCount: event.data.items.length,
  });
  break;

Test webhooks locally

Use a tunnel service to expose your local server during development:
# Using ngrok
ngrok http 3000

# Then set the webhook URL in the Dashboard to:
# https://abc123.ngrok.io/api/webhooks
The Dashboard shows a delivery log for each webhook endpoint. Use it to inspect payloads and debug failed deliveries.

Next steps