Payments

Hosted Checkout

Let a maker on your corporate account trigger and authorize a payout from inside your own ERP, Tally, or business app — no payment UI, OTP delivery, or approval logic for you to build. Everything below is self-service; you will not need to contact the bank for any of it.

What this is (and isn't)

Hosted Checkout is for a payout — money leaving your corporate account — that a real person on your side reviews and authorizes. It is not a customer-facing payment-collection product, and it is not for unattended, no-human submission. For fully machine-to-machine bulk submission, use Create Payments above instead.

1. Prerequisites

  • Generate a partner API key with the payments scope — API Keys section, this portal.
  • Register a webhook subscribed to the payment lifecycle events — Webhooks section, this portal.
  • Have at least one approved maker user and one approved beneficiary on your corporate account.

2. Create a checkout session — server-to-server only

Call this from your backend, using your secret API key. Never call it from a browser or embed the key in client code.

POST/v1/partner/payout-checkout/sessions
makerUserIdstringrequired

See the callout below — must be a real, approved human maker, never a system user.

amount.valueintegerrequired

Integer paise. ₹1,250.00 = 125000. Never send a decimal.

transactionReferencestringrequired

Your own unique reference. Safe to retry — the same Idempotency-Key returns the same session.

beneficiaryIdstringrequired

Must already be an approved beneficiary. Frozen for the whole session — the maker cannot redirect the payment elsewhere.

redirectUrl / cancelUrlstring (HTTPS URL)optional

Where the maker's browser returns to after success or cancellation. Must use https://. Registered-domain enforcement is deferred and is not provided by this release.

Request
curl -X POST https://<bank-api-host>/v1/partner/payout-checkout/sessions \
  -H "x-api-key: cms_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Idempotency-Key: order-88213-attempt-1" \
  -H "Content-Type: application/json" \
  -d '{
    "makerUserId": "user-1780460729021-815",
    "amount": { "value": 125000, "currency": "INR" },
    "transactionReference": "order-88213",
    "beneficiaryId": "bene-acme-vendor-001",
    "redirectUrl": "https://your-erp.example.com/payments/return",
    "cancelUrl": "https://your-erp.example.com/payments/cancelled",
    "metadata": { "orderId": "88213", "department": "procurement" }
  }'
201 Created
{
  "sessionId": "pcs_live_5f2a9c1e4b3d4a2e9f6c7d8e1a2b3c4d",
  "token": "9f86d081884c7d659a2feaa0c55ad015",
  "hostedUrl": "https://checkout.example-bank.com/payout/9f86d081884c7d659a2feaa0c55ad015",
  "expiresAt": 1785400200000
}
⚠ makerUserId must be a human maker, not a system user

The maker named by makerUserId is the person who receives the one-time verification code on the hosted page and authorizes the payment. It must be the user ID of an approved corporate user holding the maker (transaction.make) permission — the same kind of user who creates payments in the corporate web app today.

It must notbe a system/service account, an integration user, or any ID that doesn't correspond to a real person who can receive and enter an OTP. An unknown, unapproved, or non-maker ID is rejected outright with maker_not_authorized — there is no fallback identity.

3. Redirect the maker's browser

A full-page redirect, not an iframe embed — this is what lets one integration work identically for a browser-based ERP and a native app like Tally that can only open a system browser.

Redirect
// your frontend / ERP, after calling your own backend
// which in turn called the API above
window.location.assign(session.hostedUrl);

4. What the maker does — no work required from you

  • Reviews the frozen amount, beneficiary, and your reference.
  • Picks a package, debit account, and payment method — automatically filtered to what their role permits.
  • Requests and enters a one-time code sent to their own registered email.
  • Is redirected to your redirectUrl (or cancelUrl if they cancel).

Explicit cancellation on the hosted page first terminates the session on the server (marking it cancelled) and then returns the maker to your cancelUrl. Closing the browser tab or navigating away does not cancel the session — untouched sessions expire through the normal 15-minute TTL. Your backend can confirm cancellation by polling GET /v1/partner/payout-checkout/sessions/:sessionId.

The payout then enters the same approval matrix, CBS debit, and clearing pipeline as every other payment channel — checker approval still applies unless a no-approval tier is configured.

5. Reconcile — reuse your existing webhook integration

Store the sessionId returned when you create the session. Redirect and cancel URLs are navigation-only: they do not contain a transaction ID. Poll the session endpoint with that stored ID to reconcile the hosted checkout.

GET/v1/partner/payout-checkout/sessions/:sessionId

Before the maker completes authorization, transaction is null. Once it is present, use transaction.transactionId for Partner API authorization, transaction-status calls, and webhook correlation. A submitted checkout has been accepted for approval; it is not yet a settled payment.

200 OK — submitted session
{
  "checkout": {
    "sessionId": "pcs_live_5f2a9c1e4b3d4a2e9f6c7d8e1a2b3c4d",
    "status": "submitted",
    "expiresAt": "2026-08-01T10:30:00.000Z",
    "completedAt": "2026-08-01T10:18:42.000Z"
  },
  "transaction": {
    "transactionId": "batch-pcs-5f2a9c1e4b3d",
    "transactionReference": "order-88213",
    "status": "pending_approval"
  }
}

Correlate lifecycle webhooks by data.object.transactionId, the same transaction ID returned by session polling.

transaction.paid
{
  "id": "evt_...",
  "type": "transaction.paid",
  "created": 1785400300000,
  "data": {
    "object": {
      "object": "transaction",
      "transactionId": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
      "jobId": null,
      "apiRefNumber": "order-88213",
      "reference": "order-88213",
      "status": "paid",
      "source": "payout_checkout",
      "amount": { "value": 125000, "currency": "INR" },
      "utr": "HDFCN2026080100778",
      "bankReference": "BANKALPHA-DISP-6BA7B810...",
      "timestamps": { "paidAt": 1785400300000 }
    }
  }
}

Subscribe to webhooks for final payment outcomes. Polling the checkout session is useful for recovering the transaction ID, not for replacing lifecycle reconciliation.

Quick reference

Session TTL

~15 minutes, single-use.

Amount unit

Integer paise, always.

Actor model

makerUserId = a real, approved, human maker — never a system user.

Correlation key

transaction.transactionId after submission — returned by polling and webhooks.

Approval

Still subject to your corporate's normal checker approval matrix.

Common errors

Errors return RFC 7807 problem details with application/problem+json. Integrations branch on type and show detail to the user when appropriate — do not parse title or message text.

{
  "type": "invalid_operation_error",
  "title": "This action can't be performed in the object's current state.",
  "detail": "This payment session has expired. Start a new payment from your app.",
  "status": 409
}
ConditiontypeHTTPMeaning
Maker not authorizedinsufficient_permissions_error403makerUserId isn't an approved human maker in this corporate.
Beneficiary not foundobject_not_found_error404beneficiaryId doesn't exist or isn't approved.
Idempotency key reusedidempotency_key_already_used_error409Same Idempotency-Key, different payload — use a new key.
Idempotency in progressidempotency_key_in_progress_error409A request with this key is still processing — retry shortly.
Session expiredinvalid_operation_error409The ~15-minute session window passed — create a new session.
Session already submittedinvalid_operation_error409Already submitted — check the checkout session status.
Incorrect OTPinvalid_parameters_error400Wrong verification code entered on the hosted page.
Locked or expired OTPinvalid_operation_error409Too many wrong codes or code expired — maker starts a new payment.
OTP delivery unavailableinvalid_operation_error409No email on file for maker — contact administrator.
Role selection deniedinsufficient_permissions_error403Maker's role does not permit the selected package or debit account.
Amount limit exceededinvalid_operation_error409Over the per-transaction or daily limit for the account.
Payout creation failedinternal_server_error500Unexpected failure during payout execution.