Webhooks
Webhooks push events to your server in real time, so you don't have to poll. They're the backbone of Location Monitoring: when a customer drifts out of their zone, AfriHex POSTs the event to your endpoint.
Events
| Event | Fired when |
|---|---|
drift | A monitored customer moves out of their registered zone |
geofence_breach | A ping lands inside a defined geofence (see Monitoring) |
delivery.confirmed | A proof-of-delivery receipt is confirmed |
consent_revoked | A customer revokes location consent |
Configure a webhook
curl -X POST "https://api.afrihex.com/v2/webhooks/configure" \
-H "X-API-Key: $AFRIHEX_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://bank.example.com/webhooks/afrihex",
"secret": "a-long-random-secret",
"event_types": ["drift", "geofence_breach"]
}'
If event_types is omitted it defaults to ["drift"]. The secret is stored
encrypted and is used to sign every delivery.
Delivery format
Each delivery is a POST with Content-Type: application/json. Example drift
payload:
{
"event": "drift",
"customer_id": "cust_123",
"home_hex": "AF-GH-7-ABC123...",
"current_hex": "AF-GH-7-XYZ789...",
"distance_hops": 3,
"timestamp": "2026-08-06T08:00:00Z"
}
Headers
Every delivery includes:
| Header | Meaning |
|---|---|
X-Webhook-Event | The event type |
X-Webhook-Delivery | The delivery ID (for retries and debugging) |
X-Webhook-Signature | HMAC-SHA256 of the raw body, hex-encoded, keyed with your secret |
Verify the signature
Compute HMAC_SHA256(secret, raw_body) and hex-encode it, then compare with
X-Webhook-Signature. Use a constant-time comparison:
import {createHmac, timingSafeEqual} from 'node:crypto'
export function verify(body: Buffer, signature: string, secret: string): boolean {
const expected = createHmac('sha256', secret).update(body).digest('hex')
const a = Buffer.from(signature)
const b = Buffer.from(expected)
return a.length === b.length && timingSafeEqual(a, b)
}
Always verify the signature before trusting an event. Consider also verifying
that X-Webhook-Event matches the payload you expected.
Delivery reliability
- Deliveries are retried with exponential backoff (attempts: immediate, then
increasing delays) until the endpoint returns a
2xx. - Inspect delivery history with
GET /v2/webhooks/deliveries?days=7. - Re-send a failed delivery with
POST /v2/webhooks/retry/{deliveryId}.
Best practices
- Respond fast. Return
2xxas soon as you've accepted the event; process asynchronously. A slow or failed response triggers retries. - Idempotency. Your handler may receive the same delivery more than once —
deduplicate on
X-Webhook-Delivery. - Return
4xxto skip retries only if you truly don't want the event.