Webhooks

Preview

Real-time events delivered to your server

Outbound webhook delivery is rolling out. The events, payload shapes, and signing scheme below are the stable contract — subscribe a URL once and new event types will start flowing as they ship.

How it works
01An event happens

A customer places an order, a seating session opens, a reservation is confirmed — any state change on your restaurant emits an event.

02We send a signed POST

Tastify serializes the event as JSON and delivers it to your configured URL over HTTPS, with an X-Tastify-Signature header you can verify.

03Your server responds 2xx

Return any 2xx status within 10 seconds to acknowledge. Anything else, or a timeout, triggers an automatic retry with exponential backoff.

Inbound webhooks (partners → Tastify)

Delivery platforms and payment gateways push order and payment updates to Tastify at tenant-specific URLs. Copy these from your integrations dashboard when configuring the partner.

Delivery platforms (FoodPanda, Keeta)
POST https://your-domain.com/api/v1/webhook/delivery/{tenant_slug}/{platform}
One URL per platform per tenant. Configure the matching API key in Tastify so we can verify the payload origin.
Payment gateways (Stripe, PayMe, FPS)
POST https://your-domain.com/api/v1/webhook/payment/{tenant_slug}/{gateway}
Your gateway posts payment state changes here. Tastify reconciles them against the originating order automatically.
Generate and manage these URLs in/store-admin/integrations
Outbound events (Tastify → your URL)12 events

Every outbound event uses the same envelope — a stable id, the event name, a timestamp, the tenant id, and a data object whose shape is specific to the event.

Example envelope
{
  "id": "evt_01HKX9J8Q2MBZ7R3T5V6W4P1Y2",
  "event": "order.created",
  "created_at": "2026-04-21T10: 30: 05Z",
  "tenant_id": "550e8400-e29b-41d4-a716-000000000001",
  "data": {
    "order": {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "order_number": "ORD-001",
      "status": "pending",
      "order_type": "dine-in",
      "total_amount": 45.5,
      "table_name": "Table 3"
    }
  }
}

Orders

Fires as orders move through their lifecycle.

order.createdA new order was created.
order.updatedAn order was updated (items, notes, or status transition other than creation/completion).
order.completedAn order was fully paid and closed.
order.cancelledAn order was cancelled before completion.

Sessions

Seating session lifecycle events.

session.createdA seating session was opened at a table.
session.closedA seating session was closed; linked queue ticket marked completed and seats released.

Reservations

Reservation lifecycle events.

reservation.createdA new reservation was submitted by a customer.
reservation.confirmedA reservation was confirmed (auto or manual).
reservation.cancelledA reservation was cancelled by the customer or staff.
reservation.checked_inThe party arrived and was either queued or directly seated.
reservation.completedA reservation was marked completed after the visit.
reservation.no_showThe party did not arrive within the grace window.
Every event's full example payload is on the API reference:/api-docs
Security

Every request carries an X-Tastify-Signature header. Combine the timestamp and the raw request body with your endpoint's signing secret using HMAC-SHA256, then compare against v1 using a constant-time comparison.

Signature header
X-Tastify-Signature: t=1713696000,v1=5d41402abc…
Signing secret

Rotate the secret at any time from your integrations dashboard. Old signatures stop validating immediately after rotation.

Verification snippet
// Node.js — verify an X-Tastify-Signature header
import crypto from 'node:crypto';

export function isValidTastifySignature(
  rawBody: string,
  header: string,        // e.g. "t=1713696000,v1=5d41402abc..."
  secret: string,
) {
  const parts = Object.fromEntries(
    header.split(',').map((p) => p.split('=') as [string, string]),
  );
  const timestamp = parts.t;
  const signature = parts.v1;
  if (!timestamp || !signature) return false;

  const signedPayload = `${timestamp}.${rawBody}`;
  const expected = crypto
    .createHmac('sha256', secret)
    .update(signedPayload)
    .digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(expected, 'hex'),
    Buffer.from(signature, 'hex'),
  );
}
Delivery & retries
Exponential retries

Non-2xx responses and timeouts are retried with exponential backoff for up to 24 hours, then marked failed and surfaced in the dashboard.

Idempotency

The event id is stable across retries. Store processed ids on your side to safely ignore duplicate deliveries.

Ordering

Events are delivered in best-effort order per resource, but retries can overtake fresh events — use the created_at timestamp when ordering matters.

Looking for request/response APIs instead of events?
Browse the API reference