Skip to main content

Payments Guide

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

Overview

The MultiHub API provides three payment methods through a single endpoint:
MethodDirectionDescription
payment.inInboundCustomer pays to merchant (deposit)
payment.outOutboundMerchant pays to customer/vendor (withdrawal)
payment.statusQueryCheck the current status of a payment

Endpoint

All requests are sent to a single endpoint:
POST /public/api/multihub/v1
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 two headers:
HeaderDescriptionExample
X-Data-Application-IdYour application ID (integer)42
X-Data-HashSHA-512 hash of the request body concatenated with your secret keya1b2c3d4...
The hash is computed as:
sha512(requestBody + secretKey)
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:
{
  "method": "payment.in | payment.out | payment.status",
  "service_id": 14701,
  "params": {
    "payment": { ... }
  }
}
FieldTypeRequiredDescription
methodstringYesThe operation to perform
service_idintegerNoYour service/project identifier
paramsobjectYesContains the payment object with operation-specific fields

Response Format

All responses follow a consistent envelope:
{
  "success": true,
  "result": {
    "payment": { ... }
  },
  "request_id": "req_abc123",
  "processing_time": 42
}
FieldTypeDescription
successbooleanWhether the operation succeeded
resultobject or nullContains the payment object on success
errorobject or nullContains code, message, details, and context on failure
request_idstringUnique identifier for this request (useful for debugging)
processing_timenumberServer processing time in milliseconds

Identifiers

Every payment uses three identifiers that appear in the identifiers object:
IdentifierTypeDescription
c_idintegerClient ID — your merchant reference. You provide this when creating a payment. Must be unique per payment.
h_idintegerHub ID — system-assigned payment ID. Assigned automatically when the payment is created.
p_idstringProvider ID — the payment provider’s transaction reference. Assigned when the payment reaches the provider.
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

curl -X POST "https://api.123hub.pro/public/api/multihub/v1" \
  -H "Content-Type: application/json" \
  -H "X-Data-Application-Id: 42" \
  -H "X-Data-Hash: $(echo -n '{"method":"payment.in","service_id":14701,"params":{"payment":{"description":"Order #12345","identifiers":{"c_id":12345},"amount":{"value":10000,"currency":"INR"},"payer":{"email":"[email protected]","phone":"9876543210","person":{"first_name":"John","last_name":"Doe"},"customer_account":{"id":"ACC123"}},"client":{"language":"EN","country":"IN"}}}}YOUR_SECRET_KEY' | sha512sum | awk '{print $1}')" \
  -d '{
    "method": "payment.in",
    "service_id": 14701,
    "params": {
      "payment": {
        "description": "Order #12345",
        "identifiers": { "c_id": 12345 },
        "amount": { "value": 10000, "currency": "INR" },
        "payer": {
          "email": "[email protected]",
          "phone": "9876543210",
          "person": { "first_name": "John", "last_name": "Doe" },
          "customer_account": { "id": "ACC123" }
        },
        "client": { "language": "EN", "country": "IN" }
      }
    }
  }'

Deposit Request Fields

FieldTypeRequiredDescription
descriptionstringNoHuman-readable description for the payment
identifiers.c_idintegerYesYour unique client reference for this payment
amount.valueintegerYesPayment amount in minor units (e.g., paise for INR)
amount.currencystringYesISO 4217 currency code
payer.emailstringYesCustomer email address
payer.phonestringNoCustomer phone number
payer.person.first_namestringNoCustomer first name
payer.person.last_namestringNoCustomer last name
payer.customer_account.idstringNoCustomer account identifier in your system
client.languagestringNoISO 639-1 language code for the payment page
client.countrystringNoISO 3166-1 alpha-2 country code

Response

{
  "success": true,
  "result": {
    "payment": {
      "payer": {
        "email": "[email protected]",
        "phone": "9876543210",
        "person": { "first_name": "John", "last_name": "Doe" }
      },
      "amount": { "value": 10000, "currency": "INR" },
      "description": "Order #12345",
      "identifiers": { "c_id": 12345, "h_id": 1001, "p_id": "txn_abc123" },
      "status": {
        "status": "created",
        "final": false,
        "success": null,
        "error": null,
        "history": [
          {
            "status": "created",
            "final": false,
            "success": null,
            "created": "2026-01-15T10:30:00Z",
            "reason": null,
            "amount": 10000
          }
        ]
      },
      "timestamps": {
        "created": "2026-01-15T10:30:00Z",
        "updated": "2026-01-15T10:30:00Z"
      },
      "destination": "in",
      "service_id": 14701
    }
  },
  "request_id": "req_abc123",
  "processing_time": 42
}
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.

Creating a Withdrawal (payment.out)

Use payment.out to send money to a recipient (payout).
Withdrawals require sufficient balance in your merchant account. The API will return error code 6004 (Insufficient funds) if your balance is too low.

Request

curl -X POST "https://api.123hub.pro/public/api/multihub/v1" \
  -H "Content-Type: application/json" \
  -H "X-Data-Application-Id: 42" \
  -H "X-Data-Hash: YOUR_COMPUTED_HASH" \
  -d '{
    "method": "payment.out",
    "service_id": 14701,
    "params": {
      "payment": {
        "description": "Payout #67890",
        "identifiers": { "c_id": 67890 },
        "amount": { "value": 50000, "currency": "INR" },
        "receiver": {
          "bank": {
            "account": { "id": "1234567890" },
            "ifsc": "SBIN0001234"
          },
          "email": "[email protected]",
          "phone": "9876543210",
          "person": { "first_name": "Jane", "last_name": "Doe" }
        }
      }
    }
  }'

Withdrawal Request Fields

FieldTypeRequiredDescription
descriptionstringNoHuman-readable description for the payout
identifiers.c_idintegerYesYour unique client reference for this payment
amount.valueintegerYesPayout amount in minor units
amount.currencystringYesISO 4217 currency code
receiver.bank.account.idstringYesRecipient bank account number
receiver.bank.ifscstringDepends on regionBank routing code (IFSC for India, etc.)
receiver.emailstringNoRecipient email address
receiver.phonestringNoRecipient phone number
receiver.person.first_namestringNoRecipient first name
receiver.person.last_namestringNoRecipient last name
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.

Response

{
  "success": true,
  "result": {
    "payment": {
      "amount": { "value": 50000, "currency": "INR" },
      "description": "Payout #67890",
      "identifiers": { "c_id": 67890, "h_id": 1002, "p_id": "txn_def456" },
      "receiver": {
        "bank": {
          "account": { "id": "1234567890" },
          "ifsc": "SBIN0001234"
        },
        "email": "[email protected]",
        "phone": "9876543210",
        "person": { "first_name": "Jane", "last_name": "Doe" }
      },
      "status": {
        "status": "created",
        "final": false,
        "success": null,
        "error": null,
        "history": [
          {
            "status": "created",
            "final": false,
            "success": null,
            "created": "2026-01-15T11:00:00Z",
            "reason": null,
            "amount": 50000
          }
        ]
      },
      "timestamps": {
        "created": "2026-01-15T11:00:00Z",
        "updated": "2026-01-15T11:00:00Z"
      },
      "destination": "out",
      "service_id": 14701
    }
  },
  "request_id": "req_def456",
  "processing_time": 38
}

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

curl -X POST "https://api.123hub.pro/public/api/multihub/v1" \
  -H "Content-Type: application/json" \
  -H "X-Data-Application-Id: 42" \
  -H "X-Data-Hash: YOUR_COMPUTED_HASH" \
  -d '{
    "method": "payment.status",
    "service_id": 14701,
    "params": {
      "payment": {
        "identifiers": { "c_id": 12345 }
      }
    }
  }'

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:
{
  "method": "payment.status",
  "params": {
    "payment": {
      "identifiers": { "h_id": 1001 }
    }
  }
}

Response

The response contains the full payment object with its current status:
{
  "success": true,
  "result": {
    "payment": {
      "payer": {
        "email": "[email protected]",
        "phone": "9876543210",
        "person": { "first_name": "John", "last_name": "Doe" }
      },
      "amount": { "value": 10000, "currency": "INR" },
      "description": "Order #12345",
      "identifiers": { "c_id": 12345, "h_id": 1001, "p_id": "txn_abc123" },
      "status": {
        "status": "success",
        "final": true,
        "success": true,
        "error": null,
        "history": [
          {
            "status": "created",
            "final": false,
            "success": null,
            "created": "2026-01-15T10:30:00Z",
            "reason": null,
            "amount": 10000
          },
          {
            "status": "processing",
            "final": false,
            "success": null,
            "created": "2026-01-15T10:30:05Z",
            "reason": null,
            "amount": 10000
          },
          {
            "status": "success",
            "final": true,
            "success": true,
            "created": "2026-01-15T10:35:12Z",
            "reason": null,
            "amount": 10000
          }
        ]
      },
      "timestamps": {
        "created": "2026-01-15T10:30:00Z",
        "updated": "2026-01-15T10:35:12Z",
        "finished": "2026-01-15T10:35:12Z"
      },
      "destination": "in",
      "service_id": 14701
    }
  },
  "request_id": "req_xyz789",
  "processing_time": 12
}

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.
StatusDescriptionfinalsuccess
createdPayment created, waiting for processingfalsenull
processingPayment is being processedfalsenull
successPayment completed successfullytruetrue
errorPayment failedtruefalse
canceledPayment was cancelledtruefalse
declinedPayment expired or was declinedtruefalse
refundedFull refund processedtruetrue
partially_refundedPartial refund processedtruetrue
Terminal statuses are indicated by final: true. Once a payment reaches a terminal status, it will not transition to any other status.

Status Object Structure

Every payment includes a status object with the following fields:
{
  "status": {
    "status": "processing",
    "final": false,
    "success": null,
    "error": null,
    "history": [
      {
        "status": "created",
        "final": false,
        "success": null,
        "created": "2026-01-15T10:30:00Z",
        "reason": null,
        "amount": 10000
      },
      {
        "status": "processing",
        "final": false,
        "success": null,
        "created": "2026-01-15T10:30:05Z",
        "reason": null,
        "amount": 10000
      }
    ]
  }
}
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

// Example: Checking payment status and acting on it
const { payment } = response.result;
const { status } = payment.status;

if (payment.status.final) {
  if (payment.status.success) {
    // Payment succeeded (success, refunded, partially_refunded)
    await markOrderAsPaid(payment.identifiers.c_id);
  } else {
    // Payment failed (error, canceled, declined)
    await notifyPaymentFailed(payment.identifiers.c_id, payment.status.error);
  }
} else {
  // Payment still in progress (created, processing)
  // Wait for webhook or poll again later
}

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:
{
  "redirect": {
    "to": [
      "upi://pay?pa=merchant@bank&am=100",
      "paytmmp://pay?pa=merchant@bank&am=100",
      "gpay://upi/pay?pa=merchant@bank&am=100",
      "phonepe://pay?pa=merchant@bank&am=100"
    ]
  }
}
FieldDescription
toA URL string or array of URLs for completing the payment. For UPI, this contains deep-link URIs for different payment apps
on_failThe URL the customer is redirected to if the payment fails (when available)
on_successThe URL the customer is redirected to after a successful payment (when available)
The redirect.to field contains UPI deep-link URLs for mobile payment apps (when applicable). Provider HTTPS payment page URLs are not included in the redirect. The redirect field may be absent if no redirect URLs are available for the payment method.

Customer Data

Deposits: the payer Object

For payment.in requests, the payer object describes the customer making the payment:
{
  "payer": {
    "email": "[email protected]",
    "phone": "9876543210",
    "person": {
      "first_name": "John",
      "last_name": "Doe"
    },
    "customer_account": {
      "id": "ACC123"
    }
  }
}

Withdrawals: the receiver Object

For payment.out requests, the receiver object describes who receives the payout, including their bank details:
{
  "receiver": {
    "bank": {
      "account": { "id": "1234567890" },
      "ifsc": "SBIN0001234"
    },
    "email": "[email protected]",
    "phone": "9876543210",
    "person": {
      "first_name": "Jane",
      "last_name": "Doe"
    }
  }
}
The receiver.bank object contains the banking details required to execute the transfer. The specific fields depend on the region and payment method (e.g., ifsc for India, clabe for Mexico).

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.
// Example: Handling duplicate payment attempts
const response = await createPayment(body);

if (!response.success && response.error?.code === 6009) {
  // Payment already exists -- fetch its current status instead
  const statusResponse = await checkPaymentStatus(body.params.payment.identifiers.c_id);
  return statusResponse.result.payment;
}

Error Handling

When a request fails, the response will have success: false and include an error object. Error responses return HTTP 400:
{
  "success": false,
  "error": {
    "code": 6010,
    "message": "Payment does not exist",
    "details": null,
    "context": null
  },
  "request_id": "req_err789",
  "processing_time": 5
}
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

CodeDescriptionRecommended Action
1005Invalid request format (missing required field)Check the details field for specifics on which field is missing
6001Incorrect transaction amountVerify amount is a positive integer in minor units
6002Incorrect currency codeEnsure currency matches the service_id configuration
6004Insufficient funds (withdrawals)Top up your merchant balance before retrying
6009Payment already exists (duplicate c_id)Use payment.status to retrieve the existing payment
6010Payment does not existVerify the c_id or h_id is correct
7001Provider interaction errorRetry after a delay or contact support
8801Invalid status transitionCheck current payment status before attempting operations

Error Handling Example

import hashlib
import json
import requests

secret_key = "YOUR_SECRET_KEY"
app_id = 42

body = {
    "method": "payment.in",
    "service_id": 14701,
    "params": {
        "payment": {
            "description": "Order #12345",
            "identifiers": {"c_id": 12345},
            "amount": {"value": 10000, "currency": "INR"},
            "payer": {
                "email": "[email protected]",
                "phone": "9876543210",
                "person": {"first_name": "John", "last_name": "Doe"},
            },
        }
    },
}

body_str = json.dumps(body, separators=(",", ":"))
hash_value = hashlib.sha512((body_str + secret_key).encode()).hexdigest()

response = requests.post(
    "https://api.123hub.pro/public/api/multihub/v1",
    headers={
        "Content-Type": "application/json",
        "X-Data-Application-Id": str(app_id),
        "X-Data-Hash": hash_value,
    },
    data=body_str,
)

result = response.json()

if result["success"]:
    payment = result["result"]["payment"]
    print(f"Payment created: h_id={payment['identifiers']['h_id']}")
    if payment.get("redirect", {}).get("to"):
        print(f"Redirect customer to: {payment['redirect']['to']}")
else:
    error = result["error"]
    print(f"Error {error['code']}: {error['message']}")

    if error["code"] == 6009:
        print("Payment already exists. Fetching status...")
    elif error["code"] == 6004:
        print("Insufficient funds. Please top up.")
    elif error["code"] == 1005:
        print("Missing required field. Check error details.")

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.