IZI Webhooks: Subscribing to Events
IZI Webhooks: Subscribing to Events
Section titled “IZI Webhooks: Subscribing to Events”Webhooks let you receive IZI events in real time without constantly polling the API. Session started, balance topped up, client registered — IZI sends a POST to your endpoint automatically.
This beats polling for any integration that needs to react to events: POS sync, messenger notifications, crediting bonuses in an external system.
Step 1 — Create a Webhook
Section titled “Step 1 — Create a Webhook”mutation CreateWebhook($input: CreateWebhookInput!) { createWebhook(input: $input) { id url events secret isActive }}{ "input": { "url": "https://your-service.example.com/izi/events", "events": [ "SESSION_STARTED", "SESSION_ENDED", "BALANCE_TOPPED_UP" ], "clubId": "club_abc123" }}The response includes secret — save it immediately. It’s required for signature verification and cannot be retrieved again.
Event Types
Section titled “Event Types”| Event | When It Fires |
|---|---|
SESSION_STARTED | Gaming session starts on a device |
SESSION_ENDED | Session ends |
SESSION_PAUSED | Session paused |
BALANCE_TOPPED_UP | Client cash balance topped up |
BONUS_CREDITED | Bonus points credited |
TARIFF_PURCHASED | Client purchases a tariff |
CLIENT_REGISTERED | New client registered |
DEVICE_STATUS_CHANGED | Device goes online/offline |
SHIFT_OPENED | Shift opened by admin |
SHIFT_CLOSED | Shift closed |
ORDER_CREATED | New bar order |
PAYMENT_RECEIVED | Incoming payment |
Full up-to-date list via introspection:
{ __type(name: "WebhookEventType") { enumValues { name description } }}Step 2 — Payload Structure
Section titled “Step 2 — Payload Structure”Each event arrives as a POST with a JSON body:
{ "id": "evt_01HZ3K...", "type": "SESSION_ENDED", "timestamp": "2026-05-30T14:32:00Z", "clubId": "club_abc123", "data": { "sessionId": "sess_xyz789", "deviceId": "device_001", "clientId": "client_456", "duration": 3600, "cost": 150, "endReason": "MANUAL" }}Fields id, type, timestamp, clubId are always present. The content of data depends on event type.
Step 3 — Verify the Signature
Section titled “Step 3 — Verify the Signature”Every IZI request contains an X-IZI-Signature header — HMAC-SHA256 of the request body signed with your secret.
const crypto = require('crypto');
function verifySignature(payload, signature, secret) { const expected = crypto .createHmac('sha256', secret) .update(payload, 'utf8') .digest('hex');
return crypto.timingSafeEqual( Buffer.from(signature), Buffer.from(expected) );}
app.post('/izi/events', express.raw({ type: 'application/json' }), (req, res) => { const signature = req.headers['x-izi-signature'];
if (!verifySignature(req.body, signature, WEBHOOK_SECRET)) { return res.status(401).send('Invalid signature'); }
const event = JSON.parse(req.body); processEvent(event);
res.status(200).send('OK');});Important: use express.raw() or equivalent — you need the raw body for correct signature verification. Parsing JSON before checking will break the signature.
Idempotency
Section titled “Idempotency”IZI delivers events with at-least-once guarantee. A single event may arrive twice due to network issues. Guard against duplicates:
const processedEvents = new Set(); // in production — Redis or DB
async function processEvent(event) { if (processedEvents.has(event.id)) { return; // skip duplicate }
await handleEvent(event); processedEvents.add(event.id);}Use the event.id field as your idempotency key.
Retry Policy
Section titled “Retry Policy”If your endpoint doesn’t respond 200 OK within 5 seconds, IZI retries:
| Attempt | Delay |
|---|---|
| 2 | 30 seconds |
| 3 | 5 minutes |
| 4 | 30 minutes |
| 5 | 2 hours |
| 6 | 24 hours |
After 6 failed attempts, the webhook enters FAILED status. To reactivate:
mutation ReactivateWebhook($id: ID!) { updateWebhook(id: $id, input: { isActive: true }) { id isActive }}Managing Webhooks
Section titled “Managing Webhooks”List all webhooks for the organization:
query ListWebhooks { webhooks { id url events isActive lastDeliveredAt failureCount }}Delete:
mutation DeleteWebhook($id: ID!) { deleteWebhook(id: $id) { success }}Testing Locally
Section titled “Testing Locally”# Install ngrokbrew install ngrok
# Open tunnelngrok http 3000
# Use the generated URL when creating the webhook# https://abc123.ngrok.io/izi/eventsRelated Pages
Section titled “Related Pages”- IZI API: Overview — general architecture
- Authentication & Tokens — token for webhook registration
- API Error Codes — errors during webhook registration
- SDK Examples — ready webhook handlers for JS/Python
Frequently asked questions
Which events support webhooks?
SESSION_STARTED, SESSION_ENDED, BALANCE_TOPPED_UP, TARIFF_PURCHASED, DEVICE_STATUS_CHANGED, CLIENT_REGISTERED — and about 15 more event types. Full list via introspection: __type(name: WebhookEventType).
What does IZI do if my endpoint doesn't respond?
IZI retries with exponential backoff: 30 sec, 5 min, 30 min, 2 hours, 24 hours. After 5 failed attempts, the webhook is marked as failed and requires manual reactivation.
Can I subscribe to events from a specific club only?
Yes. When creating a webhook, specify clubId — events will be scoped to that club. Without clubId, you receive events for all clubs in the organization.
How do I test webhooks locally?
Use ngrok or Cloudflare Tunnel to get a temporary public URL. Replace it with your production endpoint later.