Targets
Preset
Local — docker compose + dotnet run
Sandbox — remit-sandbox.value.al (public, reset nightly)
Funding
Ledger
Reconciliation
Webhook secret · alpha
Webhook secret · beta
Save Probe /health/ready on all three
1 · Deposit
Account id
New account
Amount
Currency EUR USD GBP CHF
Idempotency-Key
POST /deposits
Replay same key
Same key, amount +1
GET last deposit
2 · Provider webhook (signed in the browser)
deposit — provider — reference —
Event id
Timestamp offset (seconds; −600 = stale replay)
settled
failed
settled, forged key
3 · Ledger
GET balance GET entries for last movement
4 · Withdrawal
Amount
Destination token
POST /withdrawals paid webhook failed webhook
5 · Reconciliation
The one check that does not trust the provider. This service has its own record,
built from the events above — it never reads Funding's tables. Give it the provider's statement
and it matches on the reference alone ; every difference becomes an exception it will not
fix. A person resolves those, with a reason.
Raise each exception on purpose
matched — deposit → settled webhook → Build → POST
AmountMismatch — same, but change one amount in the CSV first
UnknownAtPsp — add a row whose reference was never issued
MissingAtPsp — settle a deposit, then delete its row before posting
SettledButNotFinal — skip the webhook, but leave the reference in the CSV: the provider settled it, we never heard
Stuck — deposit, no webhook, wait for the window, then run the sweep
Download the statement, break a line in a spreadsheet, load it back and post it —
that is the loop a finance team actually runs. Post the same statement twice and nothing new is
raised: one open exception per kind, provider and reference, because month-end files get re-sent.
Provider · period (a statement is always one provider, one period)
Statement (CSV) — built from this session's settled movements; edit to break it
Build from session
Download .csv
Load .csv
POST statement
Exceptions — mine
all
Run stuck sweep
Resolve exception id · reason
POST resolve