Milestone 5.2d — Settlement webhooks and reconciliation
Status: Shipped on main (env-gated)
Tracking: ehx-api#14
Parent: ehx-web#4 checkout epic
What shipped
| Piece | Description |
|---|---|
| Idempotent confirm | checkout_tx_claims — one EVM tx hash can activate at most one session |
| Audit log | checkout_settlement_events — confirm, verify, processor, reconcile |
| Background reconcile | Polls confirming sessions with a tx hash every ~45s (RPC re-verify) |
| Processor webhook | POST /api/v1/checkout/webhooks/settlement (HMAC-signed) |
| Outbound event | checkout.session.confirmed when a session confirms |
| Operator CSV | GET /api/v1/admin/billing/settlement-events.csv |
Configuration
# Background RPC reconciliation (default on)
EHX_CHECKOUT_RECONCILE_ENABLED=1
EHX_CHECKOUT_RECONCILE_INTERVAL_SEC=45
# Inbound processor/indexer events (required to accept POSTs)
EHX_CHECKOUT_PROCESSOR_WEBHOOK_SECRET=…
# Or reuse outbound secret:
# EHX_CHECKOUT_WEBHOOK_SECRET=…
Disable reconciliation in CI or dev: EHX_CHECKOUT_RECONCILE_ENABLED=0.
Inbound processor webhook
POST /api/v1/checkout/webhooks/settlement
Headers:
X-Ehx-Signature: sha256=<hmac>(orX-Ehx-Processor-Signature)
Body (JSON):
{
"type": "payment.confirmed",
"session_id": "<checkout-session-uuid>",
"transaction_hash": "0x…"
}
Supported type values:
payment.confirmed,checkout.session.confirm,payment.settled→ idempotent confirmpayment.failed,payment.expired→ audit log only
Idempotency rules
- Same session + same tx: safe to retry (returns
already_confirmedorowned). - Different session + same tx:
409 tx_hash_already_claimed. - RPC verify success calls the same confirm path as the processor webhook.
Operator exports
| Endpoint | Purpose |
|---|---|
GET /admin/billing/checkout-export.csv | Session finance rows |
GET /admin/billing/settlement-events.csv | Settlement audit trail |
Bearer or X-Ehx-Admin-Token required.