Skip to content

IZI Webhooks: Subscribing to Events

Published: · IZI Team

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.

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.

EventWhen It Fires
SESSION_STARTEDGaming session starts on a device
SESSION_ENDEDSession ends
SESSION_PAUSEDSession paused
BALANCE_TOPPED_UPClient cash balance topped up
BONUS_CREDITEDBonus points credited
TARIFF_PURCHASEDClient purchases a tariff
CLIENT_REGISTEREDNew client registered
DEVICE_STATUS_CHANGEDDevice goes online/offline
SHIFT_OPENEDShift opened by admin
SHIFT_CLOSEDShift closed
ORDER_CREATEDNew bar order
PAYMENT_RECEIVEDIncoming payment

Full up-to-date list via introspection:

{
__type(name: "WebhookEventType") {
enumValues { name description }
}
}

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.

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.

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.

If your endpoint doesn’t respond 200 OK within 5 seconds, IZI retries:

AttemptDelay
230 seconds
35 minutes
430 minutes
52 hours
624 hours

After 6 failed attempts, the webhook enters FAILED status. To reactivate:

mutation ReactivateWebhook($id: ID!) {
updateWebhook(id: $id, input: { isActive: true }) {
id
isActive
}
}

List all webhooks for the organization:

query ListWebhooks {
webhooks {
id
url
events
isActive
lastDeliveredAt
failureCount
}
}

Delete:

mutation DeleteWebhook($id: ID!) {
deleteWebhook(id: $id) {
success
}
}
Окно терминала
# Install ngrok
brew install ngrok
# Open tunnel
ngrok http 3000
# Use the generated URL when creating the webhook
# https://abc123.ngrok.io/izi/events

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.