Skip to main content

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

EventFired when
driftA monitored customer moves out of their registered zone
geofence_breachA ping lands inside a defined geofence (see Monitoring)
delivery.confirmedA proof-of-delivery receipt is confirmed
consent_revokedA 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:

HeaderMeaning
X-Webhook-EventThe event type
X-Webhook-DeliveryThe delivery ID (for retries and debugging)
X-Webhook-SignatureHMAC-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 2xx as 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 4xx to skip retries only if you truly don't want the event.