Skip to main content
Production differs in four ways: what HEVN must enable for you, what you re-create per environment, what stops being instant, and what starts being refused. The routes, bodies and error slugs are identical. Every example on these pages reads the same variables:
Three more come out of the first two: 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.
Nothing crosses between the two environments: developer keys, clients, tokens and rail ids are per-environment, and a sandbox token is refused in production.

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 with PATCH /dapi/v1/contacts/{contactId} and book again.
  • 422 rail_requirements_unmet and 422 phone_required — a rail wants something the client has not given yet. GET /dapi/v1/banks/{rail}/requirements lists it.
  • 422 amount_below_minimum and 422 amount_above_maximum — real rail minimums replace the sandbox’s 0.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. Honour Retry-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.
Run one real payment end to end before you point customers at it: one client, one rail, one small payout to an account you control, reconciled from GET /dapi/v1/transactions. Every difference on this page shows up in that one run.

Next: Coverage

The currencies money can arrive in, the markets it can leave for, and what decides availability.