Skip to main content

Payments Guide

This guide covers everything you need to know about creating and managing payments through the 123hub API. All payment operations use a single endpoint with method-based routing.

Overview

The 123hub API provides three payment methods through a single endpoint:

Endpoint

All requests are sent to a single endpoint:
Successful responses return HTTP 200. Error responses return HTTP 400 (or HTTP 404 for unknown methods). Always check the success field in the response body to determine the outcome.

Authentication

Every request requires the application ID and exact-body hash. The timestamp and nonce are optional and must be supplied together when used: The hash is computed as:
Where requestBody is the raw JSON string of the request body, and secretKey is your merchant secret key.
Keep your secret key secure. Never expose it in client-side code, public repositories, or browser requests. The hash must be computed server-side.

Request Format

All requests follow the same envelope:

Response Format

All responses follow a consistent envelope:

Identifiers

Every payment uses three identifiers that appear in the identifiers object:
Use c_id as your primary reference for tracking payments. It is the key you control and should map to your internal order or transaction ID.

Creating a Deposit (payment.in)

Use payment.in to create a deposit where a customer pays you.
1

Build the request body

Include the payment amount, currency, customer (payer) details, and your unique c_id.
2

Compute the authentication hash

Compute sha512(requestBody + secretKey) and set it in the X-Data-Hash header.
3

Send the request

POST to /public/api/multihub/v1 and handle the response.
4

Redirect the customer

If the response includes a redirect.to URL, redirect the customer to complete payment.

Request

Deposit Request Fields

For payment.in, payer is required and must be included under params.payment.payer.

Response

For UPI payments (India), the response may include a redirect object with UPI deep-link URLs in the redirect.to field. For other payment methods, redirect may not be present in the initial response.

Full Deposit Response Examples


Creating a Withdrawal (payment.out)

Use payment.out to send money to a recipient (payout).
Withdrawals require sufficient available balance and must fit the merchant payout limits configured for the requested currency. The API returns 6004 when balance is too low, 6006 for daily limit breaches, 6005 for monthly limit breaches, and 6035 for other payout-limit rules such as since-last-settlement limits.
Use params.payment.identifiers.c_id as your own payout identifier and idempotency key. The same c_id is returned in the create response, payment.status, and payout webhooks under data.result.payment.identifiers.c_id.

Request

Region-Specific Withdrawal Examples

The receiver.bank object varies by region. Below are the key differences:

Withdrawal Request Fields

Turkey (TRY) withdrawals use bank_transfer with IBAN format in receiver.bank.account.id (e.g., TR330006100519786457841326). No ifsc or code is required.
Deposits use payer, withdrawals use receiver. The payer object describes who is sending the money (deposit), while the receiver object describes who is receiving the money (withdrawal). Both contain contact information and, for receiver, bank account details.

Tracking Withdrawal Completion

The initial payment.out response usually has status.status: "processing". Wait for a webhook or poll payment.status with your c_id or the returned h_id. Payout lifecycle webhooks use the same payment envelope as deposits. Read the business identifier from data.result.payment.identifiers.c_id; do not depend on provider-only callback fields such as order_id, transaction_id, session_token, or user_id.

Full Withdrawal Response Examples


Checking Payment Status (payment.status)

Use payment.status to query the current state of a payment. You can look up a payment by either c_id (your reference) or h_id (system-assigned ID).

Query by c_id

Query by h_id

You can also look up a payment using the system-assigned h_id. When using h_id, the service_id field is not required:

Response

The response contains the full payment object with its current status:

Re-sending Webhook (payment.notification)

Use payment.notification to trigger a webhook re-delivery for a specific payment. The request format is identical to payment.status, and the response returns the full payment object:
Provide either c_id or h_id. This method triggers a new webhook delivery to your active webhook endpoints with the current payment state. Use it only as a recovery action when your webhook handler missed or failed to process a notification. For read-only checks or polling, use payment.status with backoff instead. The method is limited to 10 requests per minute. Without operation_id, compatibility idempotency is scoped to the canonical request and current minute.

Payment Statuses

Each payment has a status object with the current status name, and boolean flags indicating whether it is final and whether it represents a success.
Terminal statuses are indicated by final: true. A terminal status is the current completed outcome for integration handling, but reconciliation, refunds, or provider corrections can publish a later status update. Always process webhooks idempotently by payment id and status timestamp.

Status Object Structure

Every payment includes a status object with the following fields:
The history array contains every status transition the payment has gone through, in chronological order. This is useful for debugging and auditing.

Handling Status Changes


Redirect URLs

For deposit payments (payment.in), the response may include a redirect object. For UPI payments (India), this contains deep-link URLs for mobile payment apps:
The redirect.to field can contain UPI deep-link URLs, a provider HTTPS payment page URL, or another provider-specific redirect target. The redirect field may be absent if no redirect URL is available for the payment method.

Deposit Response by Region

For bank transfer deposits, the response may include payment details the customer needs to complete the transfer. The exact fields depend on the region: For TRY and ARS, the receiver.bank_account object contains the bank details the customer must transfer to:
Pago Móvil and similar P2P routes may return additional typed instructions:
Only normalized instruction fields are returned. Provider-specific metadata is never copied into the public response. Existing payments are not retroactively enriched; integrations must map new provider responses to the canonical Studio bankDetails.* targets.

Customer Data

Deposits: the payer Object

For payment.in requests, the payer object describes the customer making the payment:

Withdrawals: the receiver Object

For payment.out requests, the receiver object describes who receives the payout, including their bank details:
The receiver.bank object contains the banking details required to execute the transfer. The specific fields depend on the region and payment method:

Additional Response Fields

Operations

Every payment response includes an operations array and an operation shortcut:

Identifier Formats

The h_id format varies by provider. Treat it as an opaque string:
  • Some providers return numeric strings (e.g., "1774892151645")
  • Others return UUIDs (e.g., "b8adedc9-c245-4fa2-8f26-095e8117d11a")

status.error Field

The status.error field inside the payment status object can be:
  • null — no error
  • integer — error code (e.g., 7001)
  • string — error message from provider (e.g., "Sum 150000 is lower than 200000")
Do not confuse result.payment.status.error (integer/string/null inside the payment status) with the top-level error object in error API responses (which has code, message, details, context fields).

Bank Details in payment.status

Bank details (receiver.bank) are only available in the response to payment.out creation. They are not returned in payment.status or webhook callbacks. Store them when you create the withdrawal.

Idempotency

The c_id (Client ID) serves as your idempotency key. If you send a payment.in or payment.out request with a c_id that was already used, the API will return error code 6009 (Payment already exists) instead of creating a duplicate.
Always use a unique c_id for each payment. If you receive a 6009 error, it means a payment with that c_id already exists. Use payment.status to retrieve its current state rather than creating a new one.

Error Handling

When a request fails, the response will have success: false and include an error object. Error responses return HTTP 400:
Successful responses return HTTP 200. Error responses return HTTP 400 (or HTTP 404 for unknown methods). Always check the success field in the response body to determine the outcome.

Error Codes

Error Handling Example


Best Practices

Use Unique c_id Values

Always assign a unique c_id to each payment. This serves as your idempotency key and prevents duplicate payments.

Compute Hashes Server-Side

Never expose your secret key to the client. Always compute the X-Data-Hash on your server.

Check success Field

Always check the success boolean in the response body to determine the outcome. Success returns HTTP 200, errors return HTTP 400.

Store All Identifiers

Save c_id, h_id, and p_id from the response for reconciliation, debugging, and support inquiries.

Handle Redirects

For deposits, always check for a redirect.to URL and redirect the customer to complete their payment.

Use Status History

The status.history array provides a complete audit trail. Use it for debugging and tracking payment lifecycle.