Skip to content

Add webhooks: transaction, hold and card authorization events - #49

Merged
devinpearson merged 3 commits into
mainfrom
feat/webhooks
Nov 9, 2026
Merged

devinpearson merged 3 commits into
mainfrom
feat/webhooks

Conversation

@devinpearson

Copy link
Copy Markdown
Owner

Summary

Stacked on #48 (base feat/dashboard-history-payments), because both change the transfer and payment code. Once #48 is squash-merged I'll merge main into this branch and retarget it, with no force-push.

Sandbox-only webhooks: register a URL and the sandbox POSTs events as they happen, so integrations can be built and tested against the bank's events.

Events

Event When
transaction.posted Top-ups, transfer and payment legs, created and posted swipes, settled holds
transaction.pending A card hold is placed
transaction.reversed A hold is reversed
card.authorization Every live swipe decision: approved, or why not (insufficient_funds, card_not_active, declined_by_card_code), with merchant, rand amount and any currency conversion
webhook.test On demand

Delivery

  • Format: JSON POST { id, event, createdAt, data }, with X-Sandbox-Event, X-Sandbox-Delivery and, when a secret is set, X-Sandbox-Signature: sha256=<HMAC of the raw body>.
  • Attempts: 5-second timeout and no automatic retries. Every attempt is recorded (status, response code or an error such as "Connection refused", duration) and pushed to dashboards live.
  • Never blocks: publishing runs in the background, so a slow or broken receiver never slows down or fails the API call that triggered the event.

API

/webhooks, outside /za, so no token is needed:

  • list, register, PATCH (pause/resume, change URL or events) and delete
  • send a test, the delivery log, and the event list

URLs must be http(s), and secrets are never returned. Webhooks are configuration, so Reset data and scenarios leave them in place.

OpenAPI

The endpoints, plus each event payload in 3.1's webhooks section, so receivers can generate types too.

Dashboard

A Webhooks panel:

  • register an endpoint, with event checkboxes and a signing secret
  • send a test, pause or resume, and delete
  • a live delivery log with expandable payloads

Test plan

  • API: 183 Jest tests. The new test/webhooks.spec.ts runs a real receiver on 127.0.0.1 and checks the HMAC signature, transfer legs, the hold lifecycle (authorization → pending → posted), declines and reversals, event filtering, pausing, a 500 and a connection refusal recorded with reasons, and validation.
  • Web: 39 unit tests and 16 Playwright tests. New: register through the UI, send a test, swipe a card and see the authorization delivered live.
  • openapi:check (61 routes), knip, lint, build. Web types regenerated.

🤖 Generated with Claude Code

devinpearson and others added 2 commits November 9, 2026 16:51
- Transactions panel: an account's history as the API returns it (newest
  first, pending holds on top), filtered by period (30 days, 180 days,
  all time; all time by default, as the seed data is from 2022–2023), by
  transaction type, and with or without pending holds. Signed amounts and
  the running balance.
- Beneficiaries panel: name, bank, account, category and last payment,
  and a Pay form (from account with its available balance, beneficiary,
  amount, references). Refusals show the API's reason, e.g. Insufficient
  funds.
- Open transaction lists and beneficiaries refresh with everything else.
- API: transfermultiple and paymultiple now emit the data-change event,
  so an open dashboard refreshes after transfers and payments made from
  Postman too.

Tests: 4 unit (signedAmount, periodRange, paymentError) and 3 e2e
(history filters and holds, a payment updating beneficiary and history,
a refused payment). 39 unit, 15 e2e and 173 API tests passing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sandbox-only webhooks, so integrations can react to the bank as they
would to real events.

- Webhook and WebhookDelivery tables. Endpoints under /webhooks
  (outside /za, no token): list, register (url, events, optional secret),
  PATCH to pause/resume or change, delete, send a test, delivery log, and
  the event list. URLs must be http(s); secrets are never returned.
- Events: transaction.posted (top-ups, transfer and payment legs, created
  and posted swipes, settled holds), transaction.pending,
  transaction.reversed, card.authorization (every live swipe decision:
  approved or why not, merchant, rand amount and any conversion), and
  webhook.test.
- Delivery (src/webhooks.ts): JSON POST { id, event, createdAt, data }
  with X-Sandbox-Event, X-Sandbox-Delivery and, with a secret,
  X-Sandbox-Signature (sha256 HMAC of the body). 5s timeout, no automatic
  retries; every attempt is recorded (status, response code or error such
  as "Connection refused", duration) and pushed to dashboards. Publishing
  runs in the background, so API calls never wait on or fail because of a
  receiver.
- OpenAPI: the endpoints plus each payload under 3.1's webhooks section.
- Dashboard: a Webhooks panel to register (events, signing secret), send
  tests, pause/resume and delete, with a live delivery log and payloads.

Tests: 10 API tests against a real local receiver (signature, payloads,
filtering, pausing, failures, validation) and 1 e2e test (register, test,
swipe delivered live). 183 API, 39 unit and 16 e2e passing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Base automatically changed from feat/dashboard-history-payments to main November 9, 2026 16:01
# Conflicts:
#	src/routes/accounts.ts
#	web/components/dashboard/Dashboard.tsx
#	web/hooks/useSandbox.ts
#	web/lib/api/sandbox.ts
#	web/lib/api/types.ts
@devinpearson
devinpearson merged commit 0c0a5f9 into main Nov 9, 2026
5 of 6 checks passed
@devinpearson
devinpearson deleted the feat/webhooks branch November 9, 2026 16:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant