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
Configure a webhook URL
Register your endpoint in the Crave Dashboard under Settings > Webhooks.
Crave sends a POST request
When an event occurs, Crave sends a JSON payload to your URL.
Your server processes the event
Parse the payload, verify the signature, and execute your business logic.
Return a 200 response
Acknowledge receipt within 10 seconds. Crave retries on failure.
Event types
| Event | Trigger |
|---|
order.created | Payment succeeds and order is created |
order.confirmed | Restaurant confirms the order |
order.ready | Order is ready for pickup or delivery |
order.completed | Order is fulfilled |
order.cancelled | Order is cancelled by restaurant or customer |
cart.abandoned | Cart has been inactive for the configured timeout |
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:
| Attempt | Delay |
|---|
| 1st retry | 30 seconds |
| 2nd retry | 2 minutes |
| 3rd retry | 15 minutes |
| 4th retry | 1 hour |
| 5th retry | 4 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