Webhooks
Webhooks let you subscribe to events happening inside YumKiosk and have them pushed to your own HTTP endpoint in real time. Typical use cases: syncing orders into a POS, triggering kitchen display updates, feeding a custom analytics dashboard, or updating an accounting system when an invoice is paid. This page covers how to register endpoints, how signature verification works, and the event catalog.
Registering endpoints
Go to Settings → Developers → Webhooks in the owner panel. Click New endpoint and provide:
- URL — an HTTPS endpoint on your own infrastructure. HTTP (non-TLS) is rejected.
- Events — a checkbox list of events you want to subscribe to. You can subscribe to all events or cherry-pick.
- Description — a human-readable label.
Upon save, the panel shows a signing secret — a 32-byte random string. Copy this secret and store it in your environment. You'll use it to verify incoming webhook signatures. The secret is shown once and cannot be recovered — only rotated.
Delivery
When an event fires, YumKiosk immediately POSTs a JSON body to your endpoint. The POST has these headers:
Content-Type: application/jsonYumKiosk-Signature: t=<timestamp>,v1=<hex_hmac>YumKiosk-Event: <event-name>YumKiosk-Delivery: <unique-delivery-id>User-Agent: YumKiosk-Webhook/1.0
The request body is the full event payload in JSON. We expect a 2xx response within 10 seconds. Any other response (including timeouts) is considered a failure, and we retry with exponential backoff: 1 min, 5 min, 30 min, 2 hours, 12 hours. After 5 failed attempts, the delivery is marked failed and recorded in the Webhooks → Deliveries log. The event itself is never retried again.
Signature verification
Every webhook is signed with HMAC-SHA256 using your signing secret. Verify it before trusting the payload to prevent spoofing:
import hmac, hashlib, time
def verify(payload: bytes, header: str, secret: str, tolerance: int = 300) -> bool:
parts = dict(p.split("=", 1) for p in header.split(","))
timestamp = int(parts["t"])
signature = parts["v1"]
# Reject old events (replay attack protection)
if abs(time.time() - timestamp) > tolerance:
return False
expected = hmac.new(
secret.encode(),
f"{timestamp}.{payload.decode()}".encode(),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature)
The signature is computed over the string {timestamp}.{raw_body}, matching Stripe's convention. Use constant-time comparison (hmac.compare_digest) to avoid timing attacks.
Event catalog
Current events:
session.started
Fires when a new kiosk session is created (customer tapped Tap to Order). Payload includes the session ID, kiosk ID, and creation timestamp.
session.accepted
Fires when an agent accepts a pending session. Payload adds the agent ID and accept timestamp.
session.ended
Fires when a session ends, regardless of reason. Payload includes the end reason, duration, and the full order object if applicable.
order.created
Fires when a cart transitions to a paid order. Payload is the full order object with line items, totals, and payment intent ID.
order.refunded
Fires when a manager issues a refund on a paid order. Payload includes the refund amount and reason.
kiosk.paired
Fires when a new kiosk completes pairing.
kiosk.offline
Fires when a kiosk has been offline for 2+ minutes.
agent.shift_started
Fires when an agent toggles to Available after being Offline.
agent.shift_ended
Fires when an agent toggles to Offline after being Available.
invoice.generated
Fires on the 1st of each month when a new YumKiosk invoice is created (this is the SaaS billing invoice, not a Stripe Connect customer receipt).
invoice.paid
Fires when YumKiosk successfully charges your card for a monthly invoice.
Testing webhooks
Under Webhooks → Deliveries, click any past delivery to see the full request body, response, timing, and status. You can also click Replay to re-send a delivery — useful for debugging after you fix your endpoint.
For local development, use ngrok to expose a local HTTP server as an HTTPS URL:
ngrok http 3000
# Then use the https://xxx.ngrok.io URL as your webhook endpoint.
Rate limiting
Webhooks are delivered sequentially per endpoint to preserve ordering. If your endpoint is slow, events queue up. We'll never drop events, but you may see delivery delays if you can't keep up with event volume. For high-throughput endpoints, scale horizontally and respond quickly — do the heavy lifting async after acknowledging the webhook with a 200.