Vela Pay API: integration guide

Integration guide and API reference for Vela Pay. Start with "Getting started", then create a payment and handle callbacks.

Getting started

Environments

Base URL
Sandbox https://sandbox.velapay.io
Production https://api.velapay.io

Create API keys in the merchant cabinet (https://merchant.velapay.io, Settings). Every request carries the key in Authorization: Bearer <key> or X-Api-Key: <key>. Requests are accepted only from the IP addresses allow-listed for the key. Keys can be rotated in the cabinet at any time; the previous key keeps working until you revoke it.

All amounts are strings with two decimals in INR. Timestamps are ISO 8601 in UTC.

Create a payment

Call POST /payment with the order amount, your own order reference and the webhook URL that will receive status updates.

{
  "settle_sum": "1250.00",
  "settle_ccy": "INR",
  "order_token": "order-2026-000123",
  "notify_hook": "https://merchant.example/hooks/payments",
  "rail_kind": "p2p_send"
}

Payment lifecycle

A payment moves through the states below. Poll GET /{id}/status (no more often than every 5 seconds) or, better, rely on the callback and use polling only as a fallback.

State Meaning Next step
on_hold awaiting the payer; the payer is completing the transfer intermediate, keep polling or wait for the callback
processed awaiting the payer; funds received intermediate, keep polling or wait for the callback
unsuccessful not completed final
discarded not completed; the payment window closed final
sent_back returned to the payer; awaiting the payer intermediate, keep polling or wait for the callback
contested disputed final

Only final states are stable. Never treat an intermediate state as paid. net_amount is the amount actually received and cleared_time the time the funds were confirmed.

Callbacks

Every state change is delivered with POST to the notify_hook of the payment. The body has the same shape as the status response.

Each delivery carries X-Webhook-Signature: t=<unix seconds>,v1=<hex>, where v1 is HMAC-SHA256 of the string <t>.<raw request body> computed with the webhook secret issued to you at onboarding (keep it out of your client-side code). Recompute it over the raw body exactly as received, compare in constant time and reject deliveries whose t is older than five minutes. If no webhook secret was issued for your account, the header is absent and you must fetch the payment state with the status endpoint before acting on a callback.

Respond with any 2xx status within 10 seconds. Any other response or a timeout is retried with increasing delays for about 32 hours, so your handler must be idempotent: the same event can arrive more than once. Process by movement_id and the state, not by delivery order.

Payer confirmation and receipts

On the hosted payment page the payer completes the transfer in a UPI app or by bank transfer and then confirms it either with the 12-digit bank reference (UTR) or by uploading a receipt. Vela Pay matches the confirmation against the incoming funds; the payment stays in an intermediate state until the match is complete and then moves to a final state, which you receive through the callback.

If you collect the bank reference yourself, submit it with POST /confirm together with movement_id. A reference that does not match, was already used or belongs to a different amount ends the payment with a final failure state and decline_note explaining why; the payer is offered to upload a receipt or to contact support on the page.

Errors

Errors are returned with an HTTP status and a stable code. Use the code, not the message, in your logic.

Code HTTP Message
VL_FUNDS_SHORT 402 The payer does not have enough balance to complete this inflow.
VL_MOVEMENT_MISSING 404 No movement was found for the supplied reference.
VL_ENTRY_HELD 423 This movement is held while an adjustment is being applied.
VL_SUM_OUT_OF_RANGE 422 The settle amount is outside the range allowed for this rail.
VL_TOKEN_REUSED 409 A movement already exists for this order token.
VL_CCY_NOT_ALLOWED 422 This account settles in INR only.
VL_RAIL_SILENT 502 The upstream rail gave no response; please retry shortly.
VL_RAIL_OFF 422 The chosen rail is not enabled for this merchant.
VL_RAIL_PAUSED 503 This rail is paused for maintenance right now.
VL_CHANNEL_MISMATCH 422 The channel is not valid for the selected rail.
VL_ACCOUNT_BARRED 403 This merchant account may not transact.
VL_SOURCE_ACCOUNT_BAD 422 The sender account details did not pass validation.
VL_SOURCE_NAME_BAD 422 The sender name is missing or malformed.
VL_CORE_BROKE 500 An internal fault occurred; the movement was not created.
VL_OVER_CAPACITY 503 The gateway is at capacity; please retry shortly.
VL_STATE_BROKEN 500 The movement reached an inconsistent state and was rejected.
VL_STATE_CLASH 409 The requested action conflicts with the current movement state.
VL_TOO_FAST 429 Too many requests; slow down and retry.
VL_SECRETS_DENIED 403 The supplied rail secrets were rejected.
VL_VELOCITY_HIT 429 A velocity limit was hit for this account.
VL_HOOK_BAD 422 The notify hook must be a public HTTPS URL.
VL_RETURN_BAD 422 The return link is missing or invalid.
VL_WIDGET_UNKNOWN 422 The requested widget kind is not recognised.
VL_CONCURRENCY_HIT 429 Too many concurrent operations on this movement.
VL_TOKEN_BAD 422 The order token is missing or malformed.
VL_ALREADY_SETTLED 409 This movement has already settled.
VL_SETTLE_FAULT 500 Settlement could not be recorded; support has been notified.
VL_ADJUST_BAD 422 The adjustment amount is not allowed for this movement.
VL_RECALL_BAD 422 This movement is not eligible for a send-back.
VL_PAYLOAD_BAD 422 The request payload failed schema validation.
VL_UPSTREAM_ODD 500 An upstream provider returned an unexpected response.
VL_TIMED_OUT 500 The operation timed out before the rail confirmed.
VL_LIMIT_HIT 422 The amount breaches a configured movement limit.
VL_UNSORTED_FAULT 500 An unclassified processing error occurred.
VL_UNEXPECTED 500 An unexpected error occurred while processing the request.

Validation problems (missing fields, wrong types) come back as 400 with a list of fields. 401 means the key or the source IP is not accepted. 5xx responses are safe to retry with the same order_token.

Sandbox and testing

Use the sandbox base URL with sandbox keys from https://sandbox-merchant.velapay.io. Payments there never move real money: the payment page lets you complete or fail a payment on demand, so you can test every state, the callback signature and your retry handling. Check that your endpoint answers 2xx and that repeated deliveries do not create duplicate orders.

Reconciliation

The merchant cabinet provides a settlement report (CSV or XLSX) for any period with the bank reference of every payment, gross amount, fees and net amount. Match the report against your bank statement by the bank reference. Payouts show the same breakdown per settlement.

API reference

Machine-readable OpenAPI: openapi.json next to this guide. Endpoints: /payment, /{id}/status, /confirm.