Webhooks
Inbound HMAC-verified receivers.
POST /v1/india/webhooks/glomopay
Ledger-side receiver for GlomoPay order/payment lifecycle events. Schedules a per-customer sync on money-moving events.
Headers
X-Glomopay-Signature: sha256=<hex>— constant-time HMAC-SHA256 over the raw request body usingGLOMOPAY_WEBHOOK_SECRET.
Request body
{
"entity_type": "orders" | "payment" | "payment_link",
"event_type": "paid" | "funds_available" | "success" | "failed" | "action_required" | ...,
"data": {
"id": "order_...",
"customer_id": "cust_...",
"payin_id": "...",
"status": "...",
"amount": 100000,
...
}
}
Response — 200
{
"received_at": "2026-07-01T...",
"entity_type": "orders",
"event_type": "paid",
"glomo_customer_id": "cust_x",
"order_id": "order_y",
"triggered_sync": true,
"reason": "sync scheduled in background for cust_x"
}
Status codes
200— verified, classified, ACK returned.400— malformed JSON body.401— bad or missing signature.503—GLOMOPAY_WEBHOOK_SECRETnot configured (refuses to accept unsigned payloads).
Sync-trigger predicate
def _triggers_sync(entity_type, event_type) -> bool:
if entity_type == "orders":
return event_type == "paid"
if entity_type in ("payment", "payments", "payment_link"):
return event_type in ("funds_available", "success")
return False
Only events that MOVE MONEY trigger a re-sync. Everything else is logged and
gets triggered_sync=false.
The background dispatch
When the trigger fires:
asyncio.create_task(_run_background_sync(cust_id))— held in a strong-ref set so the event loop doesn't GC it.- Runs on its OWN session under the cluster
india_sync_lock. - The webhook ACKs immediately (fast, no sender timeout risk).
Verification checklist
503when the secret isn't configured401on a bad signature200on a good HMAC +triggered_sync: truewhen the event moves money200on a good HMAC +triggered_sync: falseotherwise
Cross-team
In prod, api-global is the public-facing receiver; it verifies the sender's
signature and relays to this ledger endpoint. Both sides need a matched
GLOMOPAY_WEBHOOK_SECRET.