Every example on these pages reads the same variables:
HEVN_ACCESS_TOKEN, the one-hour token
logging in returns; HEVN_USER_ID, your own account’s cl_… id, which the
same response returns as userId and which the /client* routes need in X-Hevn-Account when you
read your own account; and HEVN_PUBLIC_KEY, the base64 public half of the PEM, which
POST /dapi/v1/payouts takes as publicKey.
What HEVN must enable
Four things are granted by HEVN rather than by the API — in the sandbox exactly as in production — and each one has a refusal that tells you it is missing.
Confirm all four the same way you would in the sandbox: create one client, read
GET /dapi/v1/banks for it, and check that the rails you expect are listed.
Nothing carries over
Keep both sets of credentials loadable at once and choose by
HEVN_API: a deploy that points a production key at the sandbox host fails at login with 401 invalid_credentials, and nothing in that refusal says you swapped a variable.
The allowlist is the second variable that fails this way. A key is pinned to the source IPs you gave it and cannot be edited, so a production deploy behind a different NAT answers 403 on every call although the key, the signature and the token are all correct. Enumerate the egress addresses of every host that will call HEVN — including the ones an autoscaler adds — before you create the production key.
The scopes are the third. They are fixed at creation too, and a route the key does not cover answers 403 forbidden with details.requiredScope. A production key that confirms payouts and maintains the contact book needs payout:sign and recipient:write; one that also runs escrow needs escrow:sign. See Developer key.
What stops being instant
In the sandbox every row exists by the time the call returns. In production most of these are queues with humans or banks behind them.
Every one of those is the same fix: a poller with backoff and a deadline, keyed on your own record. Both of the shapes you need are on the pages that produce them —
poll_until in the guides and the full loop in HTTP client.
What production starts refusing
Refusals your sandbox runs never produced, because the emulator never argues:422 contact_payment_details_invalid— the receiving bank does not recognise the beneficiary. Usually the name. Correct the contact withPATCH /dapi/v1/contacts/{contactId}and book again.422 rail_requirements_unmetand422 phone_required— a rail wants something the client has not given yet.GET /dapi/v1/banks/{rail}/requirementslists it.422 amount_below_minimumand422 amount_above_maximum— real rail minimums replace the sandbox’s0.10.attention: {"kind": "rfi"}on a payout — the partner is holding the transfer pending a question. Nothing you sign moves it until the question is answered.503 provider_unavailable— a partner is down. Retry with backoff; do not re-book.429 rate_limited— the counters are enforced in production. HonourRetry-After. See Limits.
The checklist
1
Point at the production host
Set
HEVN_API to https://api.hevn.finance/dapi/v1. Nothing else in your code changes.2
Get the flags
Integrator profile, client creation, escrow if you need it, and the rails for your markets. Verify by listing rails for a real client, not by being told.
3
Create a production developer key
In the production app, with the production egress IPs in its allowlist and the scopes that deployment needs. Save the private half the browser shows you to a file with mode
0600 on the machine that will hold it. Never copy the sandbox key, and never assume the sandbox allowlist or scopes carry over.4
Delete every /dapi/v1/sandbox call
There are four, and all four answer
404 in production. Whatever they drove — a deposit, a payin settling, a payout moving — now happens because a bank did it.5
Replace instant reads with polling
Client
ready, KYB decided, rail active, payin credited, payout settled. Back off, cap the interval, set a deadline, and record the state on your side before you act on it.6
Handle 429 and log X-Request-ID
One HTTP helper that sleeps for
Retry-After, re-mints once on 401, and logs X-Request-ID on anything 5xx. Support asks for that id first.7
Re-read your refusal handling
Especially the four funding codes:
already_funded is success, funding_in_progress means wait, funding_attempt_expired means re-open with the same key, and payment_slot_consumed means this payout can never move money — book a new one. See Errors.8
Rehearse losing the key
Create a second key before you need it, book a payout naming the new
publicKey, and confirm it. Rotation is a field on POST /dapi/v1/payouts, not a migration — but only if the second key already exists and its allowlist and scopes cover the hosts and routes that will use it. Delete the old key once the new one has carried a payment.9
Fund what production makes you fund
Escrow refunds leave your own wallet, and a payout debits the client’s. The sandbox faucet account covered both; now your balances do.
Next: Coverage
The currencies money can arrive in, the markets it can leave for, and what decides availability.