Sethara Pay API: integration guide
Integration guide and API reference for Sethara Pay. Start with "Getting started", then create a payment and handle callbacks.
Getting started
Environments
| Base URL | |
|---|---|
| Sandbox | https://beta.setharapay.io |
| Production | https://api.setharapay.io |
Create API keys in the merchant cabinet (https://admin.setharapay.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 /intake or POST /release with the order amount, your own order reference and the webhook URL that will receive status updates.
{
"book_sum": "1250.00",
"book_ccy": "INR",
"trace_key": "order-2026-000123",
"ping_url": "https://merchant.example/hooks/payments",
"leg_type": "p2p_leg"
}
trace_keymakes the call idempotent: repeating a request with the same value returns the original payment instead of creating a second one. Use your order id.ping_urlis required. It must be an HTTPS URL reachable from the internet.- The response contains
book_id(store it),book_stateandpage_url: redirect the payer to that page. It shows the payment details, opens the payer's UPI app and collects the confirmation. You do not need to render anything yourself.
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 |
|---|---|---|
booked |
awaiting the payer | intermediate, keep polling or wait for the callback |
in_clearing |
the payer is completing the transfer; awaiting the payer | intermediate, keep polling or wait for the callback |
settled |
awaiting the payer; funds received | intermediate, keep polling or wait for the callback |
declined |
not completed; the payment window closed | final |
voided |
not completed | final |
recalled |
returned to the payer; awaiting the payer; disputed | intermediate, keep polling or wait for the callback |
Only final states are stable. Never treat an intermediate state as paid. clear_sum is the amount actually received and clear_at the time the funds were confirmed.
Callbacks
Every state change is delivered with POST to the ping_url 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 book_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. Sethara 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-leg together with book_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 halt_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 |
|---|---|---|
SE_SHORT_OF_FUNDS |
402 | The payer does not hold enough balance to book this movement. |
SE_NO_SUCH_BOOKING |
404 | No movement is booked under the reference you sent. |
SE_BOOKING_HELD |
423 | This movement is held while an adjustment clears. |
SE_SUM_OFF_SCALE |
422 | The amount sits outside the band allowed on this leg. |
SE_TRACE_KEY_SEEN |
409 | A movement already exists for this trace key. |
SE_CCY_NOT_OPEN |
422 | This account clears in INR only. |
SE_LEG_SILENT |
502 | The upstream leg returned nothing; retry in a moment. |
SE_LEG_UNKNOWN |
422 | The requested leg is not wired for this account. |
SE_LEG_ASLEEP |
503 | The leg is temporarily out of service. |
SE_ROUTE_CLOSED |
422 | No open route matches this movement. |
SE_ACCOUNT_BARRED |
403 | This merchant account is barred from booking movements. |
SE_SUM_BELOW_FLOOR |
422 | The amount is under the floor set for this leg. |
SE_SUM_ABOVE_CEILING |
422 | The amount is over the ceiling set for this leg. |
SE_LEDGER_FAULT |
500 | The clearing ledger could not record this movement. |
SE_LEDGER_BUSY |
503 | The clearing ledger is busy; retry shortly. |
SE_TARIFF_FAULT |
500 | The tariff for this movement could not be resolved. |
SE_ALREADY_CLEARED |
409 | This movement has already cleared and cannot change. |
SE_DAY_CAP_HIT |
429 | The daily booking cap for this account is reached. |
SE_REGION_CLOSED |
403 | Movements from this region are not accepted on this account. |
SE_TOO_MANY_BOOKINGS |
429 | Too many movements booked in a short window; slow down. |
SE_FROM_ACCT_BAD |
422 | The counterparty account failed validation. |
SE_FROM_LABEL_BAD |
422 | The counterparty name failed validation. |
SE_BACK_URL_BAD |
422 | The return address is not a valid https URL. |
SE_LEG_SATURATED |
429 | The leg is at capacity; try again in a minute. |
SE_PAGE_KIND_BAD |
422 | The requested payment page variant does not exist. |
SE_STATE_MISMATCH |
409 | The movement is not in a state that allows this call. |
SE_CLEARING_FAULT |
500 | Clearing failed on our side; the movement was not booked. |
SE_LEG_KEYS_BAD |
422 | The leg credentials supplied are incomplete or wrong. |
SE_ADJUSTMENT_BAD |
422 | The adjustment requested is not valid for this movement. |
SE_PING_URL_BAD |
422 | The webhook address is missing, private or not https. |
SE_EDGE_FAULT |
500 | The clearing edge could not be reached. |
SE_BOOK_TIMEOUT |
500 | Booking timed out before the leg answered. |
SE_PAYLOAD_BAD |
422 | The request body did not pass validation. |
SE_UNMAPPED_FAULT |
500 | The upstream leg failed in a way we do not recognise. |
SE_BOOKING_FAULT |
500 | The movement could not be booked. Please retry or contact support. |
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 trace_key.
Sandbox and testing
Use the sandbox base URL with sandbox keys from https://beta-admin.setharapay.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: /intake, /release, /{id}/status, /confirm-leg.