Webhooks
A webhook is an HTTP POST request that Mekante sends to your server whenever an event occurs. This lets you sync memberships, earned points, redemptions, and voids into your own systems (ERP, accounting, CRM, custom messaging) without polling.
Adding an endpoint
Only the business owner can manage webhook endpoints: open Developers (Geliştiriciler) in the dashboard, click New webhook (Yeni webhook), enter your URL, select the events you want to subscribe to, and click Add endpoint (Adres ekle).
- The URL must be a publicly reachable https address. Local networks,
localhost, and private IP ranges (10.x,192.168.x,172.16–31.x, loopback, link-local) are rejected both at creation time and before every delivery attempt. - The URL must not contain inline credentials (
user:pass@). - When the endpoint is created, a signing secret (
whsec_…) is generated and shown only once. You need it to verify incoming requests (Signature verification). If you lose it, delete the endpoint and create a new one. - Choose Send test (Test gönder) from the endpoint's row menu to send a
pingevent to that endpoint.
Events
| Event | When it fires |
|---|---|
member.joined | A customer joins the business's loyalty program |
points.earned | Points or stamps are awarded (POST /earn) |
reward.redeemed | A reward is redeemed (POST /redeem) |
entry.voided | A ledger entry is voided (POST /ledger/:id/void) |
ping | Sent only when you click Send test in the dashboard, regardless of the endpoint's event filter |
Each endpoint receives only the events it subscribed to. Events are dispatched after the database transaction commits; a slow or failing webhook never blocks till operations.
Request format
POST /hooks HTTP/1.1
Content-Type: application/json
User-Agent: Mekante-Webhooks/1
X-Mekante-Event: points.earned
X-Mekante-Signature: t=1792749428,v1=794b3947df267c3354af75b5d8931538c765d768e5ca83983dcd52f7c6f93270
Every webhook payload shares the same outer envelope:
| Field | Description |
|---|---|
id | Unique delivery ID. Remains identical across retry attempts |
event | Event name |
createdAt | Timestamp when the event occurred (ISO 8601, UTC) |
data | Event-specific payload (shown below) |
member.joined
{
"id": "564c1467-e98d-434d-8722-9ffe766b1eeb",
"event": "member.joined",
"createdAt": "2026-10-23T09:57:08.193Z",
"data": {
"joinedVia": "join_code",
"membershipId": "db997e9d-3e8c-469b-b385-6d29633f7c6e",
"marketingConsent": false
}
}
joinedVia indicates how the customer enrolled: join_code (scanned a table/counter QR or entered the join code themselves) or till (enrolled at the register via SMS invite link sent by a cashier).
points.earned
{
"id": "d537115b-5aeb-47cb-9232-8e3df03070da",
"event": "points.earned",
"createdAt": "2026-10-23T09:57:08.257Z",
"data": {
"membershipId": "db997e9d-3e8c-469b-b385-6d29633f7c6e",
"points": 150,
"stamps": 0,
"balance": 150,
"stampBalance": 0,
"entryIds": ["3f99048a-4226-4482-a341-682fe57225b2"]
}
}
points is the total points awarded in the transaction (including any bonuses), stamps is the total stamps awarded, and balance / stampBalance are the member's new balances.
reward.redeemed
{
"id": "8b3d35eb-db0c-4a0e-98c8-198e5dddbc8c",
"event": "reward.redeemed",
"createdAt": "2026-10-23T09:57:08.336Z",
"data": {
"membershipId": "db997e9d-3e8c-469b-b385-6d29633f7c6e",
"rewardId": "7d5cb889-f95c-4c59-8e7d-a55a92b6113e",
"code": "WZ86BV",
"points": -100,
"stamps": 0,
"balance": 250,
"stampBalance": 3
}
}
entry.voided
data fields: membershipId, entryId (the original entry that was voided), points and stamps (the delta restored or deducted), and balance / stampBalance (the member's updated balances).
ping
{
"id": "d6cbcbea-9dfc-45fe-8a44-87c5e7f92ed9",
"event": "ping",
"createdAt": "2026-10-23T09:57:08.349Z",
"data": { "hello": "world" }
}
Responding to webhooks
Your endpoint must acknowledge receipt with any 2xx HTTP status code (such as 200). Any other status code, timeout, or connection error is treated as a failed delivery.
- Respond quickly. Requests time out after 5 seconds. Enqueue heavy processing asynchronously and return
200immediately. - Redirects are not followed. A
301or302response is treated as a failure; point the webhook URL directly at the final destination.
Retries
Failed deliveries are retried with exponential backoff according to this schedule:
| Attempt | Delay after previous attempt |
|---|---|
| 2nd | 1 minute |
| 3rd | 5 minutes |
| 4th | 30 minutes |
| 5th | 2 hours |
| 6th | 12 hours |
After the 6th attempt fails, the delivery is marked as failed and no further retries are made (roughly 14.5 hours total).
Handle duplicate deliveries idempotently
Because of retries, your endpoint may receive the same event more than once. The top-level id field is identical across all attempts for the same event: record processed id values and ignore duplicates.
Delivery order is not guaranteed. Order events by createdAt if needed, or rely on the authoritative balance and stampBalance fields included in every event.