KeySecure SP2 — webhook intake
Receives KeySecure (card provider) callbacks and stores them. No ledger posting and no card-status changes yet: those handlers arrive in SP3.
What's in it
-
POST /keysecure/webhooks(no auth guard, the callback signature authenticates it). Answers{ "code": 0 }immediately and processes in the background. - New table
keysecure_webhook_events(migration1789800000000), which keepsraw_bodyverbatim so a real sandbox callback can serve as the golden vector before signatures are enforced. - Dedupe on the unique
push_id(the envelopeid). A redelivery that carries a different body is logged as an error. -
KEYSECURE_SIG_MODE:-
log(default): a bad signature is stored, with its reason insig_result, but never reaches a handler. -
enforce: a bad signature gets 401 and nothing is stored.
-
- Typed parsing of
ConsumptionandCard_update, with amounts kept as the provider's decimal text.Authis marked IGNORED (we run in credit mode). -
registerHandler(at-least-once delivery, so handlers must be idempotent), pluslistFailed/replayFailed. - Request bodies over 64 KiB get 413. A missing KeySecure config gets 503.
Verification
-
yarn jest src/modules/keysecure: 19 suites, 222 tests pass.yarn buildsucceeds. - Booted the built app on a copy of the local DB: the migration applied and the route was mapped.
Authreturned{code:0}and was stored IGNORED, a duplicateidadded no new row,Card_updatewas PROCESSED, and a malformed body returned 400. - Final review (Opus) approved in 2 rounds.
Follow-ups (SP3)
- A replay cron for rows stuck in RECEIVED/FAILED. It must skip or re-verify rows whose signature was not verified.
- Handlers must be idempotent by business key (
consume_no/card_no+ status). The provider does not document whetheridis per delivery or per record. - Before switching
KEYSECURE_SIG_MODE=enforce: verify one real sandbox callback.