OmniPayDocs
H2H checkout

H2H overview

Build a signed API-controlled checkout with typed actions, asynchronous verification, and webhooks.

Head-to-head (H2H) deposits use the existing POST /api/v1/deposits endpoint with checkout_mode: "h2h". Omitting the field preserves the redirect-checkout contract. An H2H response can either return payment instructions for your UI or ask you to redirect the payer. Always follow the returned next_action.

Integration sequence

  1. Create or select an API key and rotate its one-time H2H signing secret.
  2. Sign the exact create request and send it from your backend.
  3. Inspect next_action.type.
  4. Continue with the matching branch:
    • For display_instructions, show the returned details, collect the payer's transaction ID, and submit it to next_action.verification.endpoint.
    • For redirect, send the payer to next_action.url. Do not call the manual verification endpoint.
  5. Poll the signed deposit status while waiting, or process signed webhooks.
  6. Fulfill only after the top-level deposit status is succeeded.

Choose the next action

next_actionYour checkout behaviorManual verification
display_instructionsRender the returned recipient, operation, amount, and currencyRequired through the returned verification.endpoint
redirectOpen the returned urlDo not call /verify; monitor status and webhooks
nullStop presenting a payment actionNot available

Do not choose an action from payment_method, and do not reconstruct payment instructions. The response is the contract for that deposit. See Next actions for the complete field-level behavior.

Minimal create request

{
  "amount": "500.00",
  "payment_method": "bkash_merchant",
  "redirect_url": "https://merchant.example/return",
  "merchant_id": "order-123",
  "checkout_mode": "h2h",
  "payer": {
    "phone": "01712345678"
  },
  "metadata": {
    "invoice_id": "INV-123"
  }
}

The request redirect_url is your return page—the destination the payer should reach after a redirect checkout finishes. It is not the URL that starts checkout. When the response has next_action.type: "redirect", send the payer only to next_action.url.

H2H currently supports bkash, nagad, bkash_agent, nagad_agent, bkash_merchant, and nagad_merchant. The amount must be a plain positive decimal string with no more than two fractional digits and must be between BDT 100 and 25,000.

The payer phone must be a valid Bangladesh mobile number. OmniPay accepts local, 88…, and 880… forms and stores the normalized local form.

Example: display instructions

{
  "id": "cm7deposit02",
  "amount": "500.00",
  "payment_method": "bkash_merchant",
  "status": "pending",
  "metadata": {
    "invoice_id": "INV-123"
  },
  "merchant_id": "order-123",
  "checkout_mode": "h2h",
  "expires_at": "2026-07-28T12:05:00.000Z",
  "next_action": {
    "type": "display_instructions",
    "channel": "bkash",
    "operation": "make_payment",
    "recipient": {
      "type": "phone",
      "value": "01800000000",
      "name": "Example Merchant"
    },
    "qr_payload": null,
    "amount": "500.00",
    "currency": "BDT",
    "verification": {
      "endpoint": "/api/v1/deposits/cm7deposit02/verify",
      "required_fields": ["provider_transaction_id"]
    }
  },
  "verification": {
    "state": "awaiting_reference",
    "attempts_used": 0,
    "attempts_remaining": 3
  },
  "provider_transaction_id": null
}

Treat the recipient identity, display name, QR payload, amount, and currency returned in next_action as authoritative for that deposit. These instructions do not change after creation.

Example: redirect

{
  "id": "cm7deposit03",
  "amount": "500.00",
  "payment_method": "bkash",
  "status": "pending",
  "metadata": {
    "invoice_id": "INV-124"
  },
  "merchant_id": "order-124",
  "checkout_mode": "h2h",
  "expires_at": "2026-07-28T12:10:00.000Z",
  "next_action": {
    "type": "redirect",
    "url": "https://pay.omnipay.page/checkout/session-123"
  },
  "verification": {
    "state": "processing",
    "attempts_used": 0,
    "attempts_remaining": 0
  },
  "provider_transaction_id": null
}

Send the payer to next_action.url before expires_at. Do not submit a transaction ID for this action. After the payer returns to your redirect_url, show a processing state while your backend reads the signed deposit status. Treat the browser return as navigation only; fulfill from status: "succeeded" or a verified deposit.succeeded webhook.

Handle the union exhaustively

const action = deposit.next_action;

if (action === null) {
  showNonActionableState(deposit.status);
} else {
  switch (action.type) {
    case "display_instructions":
      showPaymentInstructions(action);
      break;

    case "redirect":
      window.location.assign(action.url);
      break;

    default:
      assertNever(action);
  }
}

function assertNever(value: never): never {
  throw new Error(`Unsupported next action: ${JSON.stringify(value)}`);
}

Keep fulfillment separate from action handling. A displayed instruction, successful browser return, or completed redirect is not proof of payment; only the signed deposit status or verified webhook is authoritative.

Availability

H2H must be enabled for your OmniPay organization. If an H2H request returns 403, contact your OmniPay account manager to confirm access.

Compatibility

Redirect checkout remains bearer-only and keeps its existing request and response contract. Adding H2H support does not change existing redirect transactions.

On this page