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
Three 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 three the same way you would in the sandbox: link 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 allowed sites are the third. A redirectUri the production window does not list is refused before any popup opens, so add the production callback origin before the first customer tries to sign in.
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 anyone signs 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, 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. 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 carries over.4
Configure the production sign-in window
In the production app, under Settings → Partner program → Developer → Sign-in window: your title, logo and colours, and every production origin your callback page is served from under Allowed sites. Check that your server binds each session’s
state to the browser and keeps the client id in its own session — Sign in with HEVN.5
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.6
Replace instant reads with polling
Client
ready, KYB decided, rail active, payin credited, payout settled after its holder approved it. Back off, cap the interval, set a deadline, and record the state on your side before you act on it.7
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.8
Re-read your refusal handling
The sign-in window’s results —
access_denied, already_registered, already_linked, expired — and, for payouts from your own account, 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.9
Rehearse losing the key
Create a second key before you need it, log in with it, and book a payout from your own account naming the new
publicKey. Rotation is a field on POST /dapi/v1/payouts, not a migration — but only if the second key already exists and its allowlist covers the hosts that will use it. Delete the old key once the new one has carried a request.10
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 and your customers’ do.
Next: Coverage
The currencies money can arrive in, the markets it can leave for, and what decides availability.