Skip to main content
Work through this page before you switch any VelaFi integration from the sandbox to production. The shared sections apply to every integration. The last section links to the checklist on each use-case page. Most production incidents we see come from three gaps: webhook endpoints that do not acknowledge deliveries in the required format, retries without a clientId, and no reconciliation job to catch what webhooks missed.

Credentials and authentication

  • Keep sandbox and production credentials apart. Sandbox keys come from VelaFi and are used with https://api-test.velafi.com. Production keys are created on the API Management page and are used with https://api.velafi.com. The two sets are not interchangeable. See Sandbox vs. Production Keys.
  • Add the public IP address of every production server that calls the API to the key’s IP whitelist (up to 5 addresses per key, no CIDR ranges). A key without a whitelist cannot get a token (-1023), and calls from other addresses fail with -2015 (token request) or -2019 (API call). Update the list before you move servers or change egress IPs.
  • Keep the API key, API secret and tokens on your server. Never put them in browser or mobile code, logs, or source control.
  • Cache the token and request a new one before its expireTime (Unix milliseconds). If a call returns -1022, the token expired or is unknown: get a new token and send the call again.
  • Plan key rotation: create the new key, whitelist its IPs, switch over, then delete the old key. See Security Best Practices.

Webhooks

Webhooks do not fire in the sandbox, so production is the first place your handler receives real deliveries. Check it carefully.
  • Register a production webhook for each event type you use with Create Webhook (POST /v2/webhook), using your production credentials. Each event type (ORDER_WEBHOOK, GLOBAL_PAYMENT_WEBHOOK, FUNDING_WEBHOOK, ON_RAMP_WITHDRAW_WEBHOOK, CRYPTO_WITHDRAW_WEBHOOK, STABLE_COIN_PAYMENT_WEBHOOK) needs its own registration. Events that happen while no active webhook exists for their type are not queued, so register before your first live transaction.
  • Store each webhook’s publicKey and verify the RSA-SHA256 signature header against the raw request body bytes, before you parse or re-serialize the JSON. See Webhook Setup.
  • Acknowledge every delivery you accept with HTTP 2xx and exactly this JSON body. If signature verification fails, return {"callbackStatus":"FAIL"} so the delivery is retried.
Anything else counts as a failed delivery and triggers a retry, even if your system processed the event. Each of these is a failure:
  • A plain-text SUCCESS, success or OK
  • {"callbackStatus":"success"} (the value is case-sensitive)
  • {"received":true}, {"status":"ok"} or any other JSON without "callbackStatus":"SUCCESS"
  • An empty body, or a body with whitespace or a byte-order mark before the opening {
  • Your own error JSON, or any non-2xx status
  • No response before the delivery times out
  • Respond fast. Verify the signature, store the event, return the acknowledgement, and do slow work (database updates, calls to other systems) asynchronously.
  • Handle duplicates. The same event can arrive more than once. Make processing idempotent on the event type, the record ID (orderId, txId or withdrawId) and the status.
  • Do not rely on order or on seeing every status. Deliveries can arrive out of order, and an intermediate status can be skipped when the record has already moved on. Treat a webhook as a signal: read the record’s current state with the matching query endpoint and act on that.
  • Reconcile what webhooks miss. A failed delivery is tried 3 times in total (the second attempt about 5–10 minutes after the first, the third about 60–65 minutes after the second). After that the event is dropped, and the API has no redelivery endpoint. Run the reconciliation job described below.
  • Test your handler’s response before go-live. Send a sample payload to your own endpoint and check that the raw response is exactly {"callbackStatus":"SUCCESS"} with a 2xx status.

Idempotency with clientId

Send a new, unique clientId on every call that creates something, store it before you send the request, and reuse the same value when you retry that request. VelaFi rejects a reused clientId instead of creating a second record. The duplicate response does not return the original record, so look it up. A clientId stays bound to its record permanently, including after the record is cancelled. Use a new value for a new operation. Create Merchant has no clientId. Before you retry it, look for the merchant by email with List Merchants (status=-1).

Error handling

  • Branch on the code field, not on the HTTP status. Errors use HTTP 400 (or 500 for -1001) with a negative code (except 30012). There is no 401 or 429. See Error Codes.
  • Treat -1001, -1006, -1007 and 30012 on a call that creates or changes something as outcome unknown. The operation may have completed. Look it up by clientId (table above) and retry with the same clientId only if nothing is there. A -1174 on that retry means the first request went through.
  • Do not match on msg. It can contain runtime values and placeholders.
  • Back off on -1006 from a rate-limited endpoint and on 30012 (too many concurrent requests).
  • For failed orders, read failCode and failReason from Get Order. failReason can be empty or contain the provider’s raw text, so log it and handle the order by its status.

Reconciliation

Webhooks are a notification channel, not your ledger. Run a scheduled job that reads VelaFi’s records for the last few hours or days and compares them with your own.
  • Re-check every record your system holds in a non-final state, so a missed webhook does not leave an order or payout pending on your side forever.
  • Alert on records that stay in a non-final status longer than you expect for that rail.
  • Reconcile balances daily: opening balance plus funding records should equal the closing balance.

Limits

  • Check each merchant’s daily and monthly limits and usage (dayFiatLimit, monthFiatLimit, monthCryptoLimit and the matching ...Used fields) with List Merchants, and per-channel limits (monthBuyLimit, monthSellLimit) with List Merchant Accounts.
  • Handle limit errors: -5050 and -5060 (order below the minimum or above the maximum), -5029, -5030 and -5081 (monthly or daily limit exceeded).
  • Request higher limits before launch if your expected volume needs them. See Transaction Limits.
  • Respect the rate limits on quote and order endpoints.

Monitoring

  • Log every request and response with your clientId and VelaFi’s ID (orderId, txId, withdrawId), so you can trace any transaction.
  • Alert on token failures (-1022, -1023, -2015, -2019). They usually mean an expired token, a disabled key or an IP that is not whitelisted.
  • Alert when webhook deliveries stop arriving or when your handler returns anything other than the acknowledgement.
  • Alert on rising error rates, on outcome-unknown codes, and on orders or payouts stuck in non-final statuses.
  • Watch the balances you pay from (fiat balance for payouts and prefunded orders, crypto balance for off-ramps) so payouts do not fail for lack of funds.

Per use case

Each use-case page ends with a checklist for its own flow. Complete the one for every flow you launch:

Collect with virtual accounts

Virtual account per payer, FUNDING_WEBHOOK handling, deposit matching by depositAlias.

Pay out from your balance

Beneficiaries, balance and fee checks, payout status tracking.

Convert to stablecoins

Prefunded or pay-per-order on-ramp orders, quotes, expiry handling.

Pay out funded with stablecoins

Off-ramp orders, crypto balance, payout failures and refunds.

Onboard your customers

Merchant creation, verification status, channel activation.

Cross-border payments

Fiat-to-fiat orders with GLOBAL_PAYMENT_WEBHOOK.