> ## Documentation Index
> Fetch the complete documentation index at: https://docs.bafanglaicai88.com/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> Describe only the public 123hub merchant API documented on this site. The primary endpoint is POST /public/api/multihub/v1.
> Preserve API method names, field names, header names, and error codes exactly as documented. Do not invent endpoints or parameters.
> Treat every amount as an integer in minor units unless a page explicitly states otherwise.
> Never expose, request, or fabricate a merchant secret key. The SDK pages contain reference implementations, not official SDK packages.

# Testing Guide

> Test your integration before going live

# Testing Guide

Use this guide to validate request signing, payment creation, status polling, balances, webhooks, and error handling before production traffic.

## Test vs Production Mode

<Info>
  Test and production use the **same API endpoint** (`POST /public/api/multihub/v1`). The environment is determined by your credentials and the service routes assigned during onboarding. Your 123hub account manager will confirm which test credentials and low-value routes are available for your account.
</Info>

| Credential Set                             | Environment | Behavior                                                        |
| ------------------------------------------ | ----------- | --------------------------------------------------------------- |
| Test `application_id` + `secret_key`       | Test        | Account-specific integration routes confirmed during onboarding |
| Production `application_id` + `secret_key` | Production  | Real transactions, actual money movement                        |

## Getting Test Credentials

Test credentials are provided by the 123hub team during onboarding. You will receive:

* **application\_id** — an integer identifying your test application
* **secret\_key** — a private key used to sign requests in test mode

<Steps>
  <Step title="Request credentials">
    Contact your 123hub account manager or [support@123hub.pro](mailto:support@123hub.pro) to request test credentials.
  </Step>

  <Step title="Verify connectivity">
    Use the `gateway.ping` method to confirm your credentials work.
  </Step>
</Steps>

<Info>
  Contact [support@123hub.pro](mailto:support@123hub.pro) if you need test credentials.
</Info>

## Test Credential Behavior

### Payments

* Payments are created and go through normal status transitions
* Route behavior depends on the account and `service_id` configuration confirmed during onboarding
* Webhooks are delivered normally
* All API responses mirror production behavior

### Balance

* `balance.get` returns the balances configured for the credential environment
* Confirm available currencies and balance funding with your account manager before withdrawal tests
* Balance changes follow the same response format as production

### Webhooks

* Webhooks are delivered to your configured endpoints
* Use webhook testing to verify your integration
* Same signature verification as production

## Payment Scenarios

### Successful Deposit

Create a deposit payment using `payment.in` -- it will transition through `created` to `success` status:

<CodeGroup>
  ```bash cURL theme={null}
  BODY='{"method":"payment.in","service_id":14701,"params":{"payment":{"description":"Test deposit order","identifiers":{"c_id":"99001"},"amount":{"value":10000,"currency":"INR"},"payer":{"email":"test@example.com","phone":"9876543210","person":{"first_name":"Test","last_name":"Customer"}},"client":{"language":"EN","country":"IN"}}}}'
  HASH=$(printf '%s%s' "$BODY" "YOUR_SECRET_KEY" | sha512sum | awk '{print $1}')
  TIMESTAMP=$(date +%s)
  NONCE=$(uuidgen | tr '[:upper:]' '[:lower:]')

  curl -X POST "https://api.bafanglaicai88.com/public/api/multihub/v1" \
    -H "Content-Type: application/json" \
    -H "X-Data-Application-Id: 1" \
    -H "X-Data-Hash: ${HASH}" \
    -H "X-Data-Timestamp: ${TIMESTAMP}" \
    -H "X-Data-Nonce: ${NONCE}" \
    --data-binary "${BODY}"
  ```

  ```python Python theme={null}
  import requests
  import hashlib
  import json
  import time
  import uuid

  APPLICATION_ID = 1
  SECRET_KEY = "your_test_secret_key"
  URL = "https://api.bafanglaicai88.com/public/api/multihub/v1"

  payload = {
      "method": "payment.in",
      "service_id": 14701,
      "params": {
          "payment": {
              "description": "Test deposit order",
              "identifiers": {"c_id": "99001"},
              "amount": {"value": 10000, "currency": "INR"},
              "payer": {
                  "email": "test@example.com",
                  "phone": "9876543210",
                  "person": {"first_name": "Test", "last_name": "Customer"}
              },
              "client": {"language": "EN", "country": "IN"}
          }
      }
  }

  body = json.dumps(payload, separators=(",", ":"))
  signature = hashlib.sha512((body + SECRET_KEY).encode()).hexdigest()

  headers = {
      "Content-Type": "application/json",
      "X-Data-Application-Id": str(APPLICATION_ID),
      "X-Data-Hash": signature,
      "X-Data-Timestamp": str(int(time.time())),
      "X-Data-Nonce": str(uuid.uuid4())
  }

  response = requests.post(URL, data=body, headers=headers, timeout=(3.05, 30))
  print(response.json())
  ```

  ```javascript Node.js theme={null}
  const crypto = require('crypto');
  const axios = require('axios');

  const APPLICATION_ID = 1;
  const SECRET_KEY = 'your_test_secret_key';
  const URL = 'https://api.bafanglaicai88.com/public/api/multihub/v1';

  const payload = {
    method: 'payment.in',
    service_id: 14701,
    params: {
      payment: {
        description: 'Test deposit order',
        identifiers: { c_id: '99001' },
        amount: { value: 10000, currency: 'INR' },
        payer: {
          email: 'test@example.com',
          phone: '9876543210',
          person: { first_name: 'Test', last_name: 'Customer' }
        },
        client: { language: 'EN', country: 'IN' }
      }
    }
  };

  const body = JSON.stringify(payload);
  const signature = crypto.createHash('sha512').update(body + SECRET_KEY).digest('hex');
  const timestamp = Math.floor(Date.now() / 1000).toString();
  const nonce = crypto.randomUUID();

  const response = await axios.post(URL, body, {
    headers: {
      'Content-Type': 'application/json',
      'X-Data-Application-Id': String(APPLICATION_ID),
      'X-Data-Hash': signature,
      'X-Data-Timestamp': timestamp,
      'X-Data-Nonce': nonce
    },
    timeout: 30000
  });

  console.log(response.data);
  ```
</CodeGroup>

### Successful Deposit (Turkey)

Create a TRY deposit using the `havale` payment method:

<CodeGroup>
  ```bash cURL theme={null}
  BODY='{"method":"payment.in","service_id":15001,"params":{"payment":{"description":"Test deposit Turkey","identifiers":{"c_id":"99003"},"amount":{"value":50000,"currency":"TRY"},"payer":{"email":"test@example.com","phone":"5321234567","person":{"first_name":"Mehmet","last_name":"Yilmaz"}},"client":{"language":"EN","country":"TR"}}}}'
  HASH=$(printf '%s%s' "$BODY" "YOUR_SECRET_KEY" | sha512sum | awk '{print $1}')
  TIMESTAMP=$(date +%s)
  NONCE=$(uuidgen | tr '[:upper:]' '[:lower:]')

  curl -X POST "https://api.bafanglaicai88.com/public/api/multihub/v1" \
    -H "Content-Type: application/json" \
    -H "X-Data-Application-Id: 1" \
    -H "X-Data-Hash: ${HASH}" \
    -H "X-Data-Timestamp: ${TIMESTAMP}" \
    -H "X-Data-Nonce: ${NONCE}" \
    --data-binary "${BODY}"
  ```

  ```python Python theme={null}
  import requests
  import hashlib
  import json
  import time
  import uuid

  APPLICATION_ID = 1
  SECRET_KEY = "your_test_secret_key"
  URL = "https://api.bafanglaicai88.com/public/api/multihub/v1"

  payload = {
      "method": "payment.in",
      "service_id": 15001,
      "params": {
          "payment": {
              "description": "Test deposit Turkey",
              "identifiers": {"c_id": "99003"},
              "amount": {"value": 50000, "currency": "TRY"},
              "payer": {
                  "email": "test@example.com",
                  "phone": "5321234567",
                  "person": {"first_name": "Mehmet", "last_name": "Yilmaz"}
              },
              "client": {"language": "EN", "country": "TR"}
          }
      }
  }

  body = json.dumps(payload, separators=(",", ":"))
  signature = hashlib.sha512((body + SECRET_KEY).encode()).hexdigest()

  headers = {
      "Content-Type": "application/json",
      "X-Data-Application-Id": str(APPLICATION_ID),
      "X-Data-Hash": signature,
      "X-Data-Timestamp": str(int(time.time())),
      "X-Data-Nonce": str(uuid.uuid4())
  }

  response = requests.post(URL, data=body, headers=headers, timeout=(3.05, 30))
  print(response.json())
  ```

  ```javascript Node.js theme={null}
  const crypto = require('crypto');
  const axios = require('axios');

  const APPLICATION_ID = 1;
  const SECRET_KEY = 'your_test_secret_key';
  const URL = 'https://api.bafanglaicai88.com/public/api/multihub/v1';

  const payload = {
    method: 'payment.in',
    service_id: 15001,
    params: {
      payment: {
        description: 'Test deposit Turkey',
        identifiers: { c_id: '99003' },
        amount: { value: 50000, currency: 'TRY' },
        payer: {
          email: 'test@example.com',
          phone: '5321234567',
          person: { first_name: 'Mehmet', last_name: 'Yilmaz' }
        },
        client: { language: 'EN', country: 'TR' }
      }
    }
  };

  const body = JSON.stringify(payload);
  const signature = crypto.createHash('sha512').update(body + SECRET_KEY).digest('hex');
  const timestamp = Math.floor(Date.now() / 1000).toString();
  const nonce = crypto.randomUUID();

  const response = await axios.post(URL, body, {
    headers: {
      'Content-Type': 'application/json',
      'X-Data-Application-Id': String(APPLICATION_ID),
      'X-Data-Hash': signature,
      'X-Data-Timestamp': timestamp,
      'X-Data-Nonce': nonce
    },
    timeout: 30000
  });

  console.log(response.data);
  ```
</CodeGroup>

<Note>
  Turkey (TRY) deposit methods: `havale`, `papara`, `kredikarti`. Withdrawals use `bank_transfer` with IBAN format.
</Note>

### Successful Withdrawal

Create a withdrawal payment using `payment.out` (requires sufficient available balance on the configured route):

<CodeGroup>
  ```bash cURL theme={null}
  BODY='{"method":"payment.out","service_id":14701,"params":{"payment":{"description":"Test payout","identifiers":{"c_id":"99002"},"amount":{"value":5000,"currency":"INR"},"receiver":{"bank":{"account":{"id":"123456789012"},"ifsc":"SBIN0001234"},"email":"payouts@example.com","person":{"first_name":"Juan","last_name":"Perez"}},"client":{"language":"EN","country":"IN"}}}}'
  HASH=$(printf '%s%s' "$BODY" "YOUR_SECRET_KEY" | sha512sum | awk '{print $1}')
  TIMESTAMP=$(date +%s)
  NONCE=$(uuidgen | tr '[:upper:]' '[:lower:]')

  curl -X POST "https://api.bafanglaicai88.com/public/api/multihub/v1" \
    -H "Content-Type: application/json" \
    -H "X-Data-Application-Id: 1" \
    -H "X-Data-Hash: ${HASH}" \
    -H "X-Data-Timestamp: ${TIMESTAMP}" \
    -H "X-Data-Nonce: ${NONCE}" \
    --data-binary "${BODY}"
  ```

  ```python Python theme={null}
  import requests
  import hashlib
  import json
  import time
  import uuid

  APPLICATION_ID = 1
  SECRET_KEY = "your_test_secret_key"
  URL = "https://api.bafanglaicai88.com/public/api/multihub/v1"

  payload = {
      "method": "payment.out",
      "service_id": 14701,
      "params": {
          "payment": {
              "description": "Test payout",
              "identifiers": {"c_id": "99002"},
              "amount": {"value": 5000, "currency": "INR"},
              "receiver": {
                  "bank": {
                      "account": {"id": "123456789012"},
                      "ifsc": "SBIN0001234"
                  },
                  "email": "payouts@example.com",
                  "person": {"first_name": "Juan", "last_name": "Perez"}
              },
              "client": {"language": "EN", "country": "IN"}
          }
      }
  }

  body = json.dumps(payload, separators=(",", ":"))
  signature = hashlib.sha512((body + SECRET_KEY).encode()).hexdigest()

  headers = {
      "Content-Type": "application/json",
      "X-Data-Application-Id": str(APPLICATION_ID),
      "X-Data-Hash": signature,
      "X-Data-Timestamp": str(int(time.time())),
      "X-Data-Nonce": str(uuid.uuid4())
  }

  response = requests.post(URL, data=body, headers=headers, timeout=(3.05, 30))
  print(response.json())
  ```

  ```javascript Node.js theme={null}
  const crypto = require('crypto');
  const axios = require('axios');

  const APPLICATION_ID = 1;
  const SECRET_KEY = 'your_test_secret_key';
  const URL = 'https://api.bafanglaicai88.com/public/api/multihub/v1';

  const payload = {
    method: 'payment.out',
    service_id: 14701,
    params: {
      payment: {
        description: 'Test payout',
        identifiers: { c_id: '99002' },
        amount: { value: 5000, currency: 'INR' },
        receiver: {
          bank: {
            account: { id: '123456789012' },
            ifsc: 'SBIN0001234'
          },
          email: 'payouts@example.com',
          person: { first_name: 'Juan', last_name: 'Perez' }
        },
        client: { language: 'EN', country: 'IN' }
      }
    }
  };

  const body = JSON.stringify(payload);
  const signature = crypto.createHash('sha512').update(body + SECRET_KEY).digest('hex');
  const timestamp = Math.floor(Date.now() / 1000).toString();
  const nonce = crypto.randomUUID();

  const response = await axios.post(URL, body, {
    headers: {
      'Content-Type': 'application/json',
      'X-Data-Application-Id': String(APPLICATION_ID),
      'X-Data-Hash': signature,
      'X-Data-Timestamp': timestamp,
      'X-Data-Nonce': nonce
    },
    timeout: 30000
  });

  console.log(response.data);
  ```
</CodeGroup>

### Check Payment Status

Query the status of a payment using `payment.status`:

```bash theme={null}
BODY='{"method":"payment.status","params":{"payment":{"identifiers":{"h_id":"1001"}}}}'
HASH=$(printf '%s%s' "$BODY" "YOUR_SECRET_KEY" | sha512sum | awk '{print $1}')
TIMESTAMP=$(date +%s)
NONCE=$(uuidgen | tr '[:upper:]' '[:lower:]')

curl -X POST "https://api.bafanglaicai88.com/public/api/multihub/v1" \
  -H "Content-Type: application/json" \
  -H "X-Data-Application-Id: 1" \
  -H "X-Data-Hash: ${HASH}" \
  -H "X-Data-Timestamp: ${TIMESTAMP}" \
  -H "X-Data-Nonce: ${NONCE}" \
  --data-binary "${BODY}"
```

### Error Response Example

Errors are returned with HTTP 400 (or HTTP 404 for unknown methods) and `success: false`. Always check the `success` field. Here is an example of an invalid signature error:

```json theme={null}
{
  "success": false,
  "error": {
    "code": 3000,
    "message": "Authentication error",
    "details": null,
    "context": null
  },
  "request_id": "f6a7b8c9-d0e1-2345-abcd-456789012345",
  "processing_time": 3
}
```

## Testing Webhooks

### Local Development

For local development, use a tunneling service like [ngrok](https://ngrok.com) to expose your local server:

```bash theme={null}
# Start ngrok
ngrok http 3000

# Use the ngrok URL for your webhook endpoint
# https://abc123.ngrok.io/webhooks/123hub
```

## Integration Checklist

Before going live, verify your integration handles these scenarios:

<AccordionGroup>
  <Accordion title="Authentication">
    * [ ] Compute `X-Data-Hash` correctly as `sha512(requestBody + secretKey)`
    * [ ] Send required `X-Data-Application-Id` and a correct `X-Data-Hash`; if testing replay proof, send both `X-Data-Timestamp` and `X-Data-Nonce`
    * [ ] Verify `gateway.ping` returns a successful response
    * [ ] Store credentials securely (environment variables, not hardcoded)
  </Accordion>

  <Accordion title="Payment Creation">
    * [ ] Create deposit payments (`payment.in`) successfully
    * [ ] Create withdrawal payments (`payment.out`) successfully
    * [ ] Handle error responses gracefully (check `success` field)
    * [ ] Store `h_id` from response for subsequent status queries
    * [ ] Send unique `c_id` per payment to avoid duplicates (error 6009)
  </Accordion>

  <Accordion title="Payment Status">
    * [ ] Retrieve payment status using `payment.status` with `h_id`
    * [ ] Handle all status values: `created`, `processing`, `success`, `error`, `canceled`, `declined`, `refunded`, `partially_refunded`
    * [ ] Check the `final` field to determine if a payment has reached a terminal state
    * [ ] Check the `success` field to determine if a final payment was successful
  </Accordion>

  <Accordion title="Webhooks">
    * [ ] Receive webhook notifications
    * [ ] Verify webhook signatures
    * [ ] Handle payment completion events
    * [ ] Handle payment failure events
    * [ ] Process events idempotently
  </Accordion>

  <Accordion title="Error Handling">
    * [ ] Success returns HTTP 200, errors return HTTP 400/404 -- always check `success` field
    * [ ] Handle error code 3000 (authentication error / invalid signature)
    * [ ] Handle error code 3003 (application not found)
    * [ ] Handle error code 2002 (no route available)
    * [ ] Handle error code 6004 (insufficient funds for withdrawals)
    * [ ] Handle error code 1005 (missing or invalid fields)
    * [ ] Handle network timeouts and retry with the same `c_id`
  </Accordion>

  <Accordion title="Balance">
    * [ ] Check balance using `balance.get` before withdrawals
    * [ ] Handle insufficient funds errors (error code 6004)
  </Accordion>
</AccordionGroup>

## Error Code Reference

| Code | Description                                     |
| ---- | ----------------------------------------------- |
| 1000 | Common error                                    |
| 1002 | Method not available (HTTP 404)                 |
| 1005 | Invalid request format (missing required field) |
| 2002 | Payment provider not available                  |
| 3000 | Authentication error (invalid signature)        |
| 3003 | Application not found                           |
| 6001 | Incorrect transaction amount                    |
| 6002 | Incorrect currency code                         |
| 6004 | Insufficient funds                              |
| 6005 | Monthly limit exceeded                          |
| 6006 | Daily limit exceeded                            |
| 6009 | Payment already exists (duplicate `c_id`)       |
| 6010 | Payment does not exist                          |
| 6035 | Exceeded payments                               |
| 7001 | Provider interaction error                      |
| 8600 | Invalid bank credentials                        |
| 8801 | Invalid status transition                       |
| 9000 | Validation error                                |

## Going Live

<Steps>
  <Step title="Complete Testing">
    Ensure all checklist items pass with the test credentials and routes assigned to your account
  </Step>

  <Step title="Request Production Credentials">
    Contact your 123hub account manager or [support@123hub.pro](mailto:support@123hub.pro) to request production `application_id` and `secret_key`
  </Step>

  <Step title="Update Configuration">
    Replace test `application_id` and `secret_key` with production credentials in your production environment
  </Step>

  <Step title="Update Webhook URLs">
    Ensure webhooks point to your production server
  </Step>

  <Step title="Monitor">
    Watch the dashboard for your first live transactions
  </Step>
</Steps>

<Warning>
  Never use production credentials in development or testing environments. Always keep test and production credentials separate.
</Warning>

## Common Testing Mistakes

<CardGroup cols={2}>
  <Card title="Using Production Credentials in Dev" icon="triangle-exclamation">
    Always use test `application_id` and `secret_key` in development
  </Card>

  <Card title="Skipping Signature Verification" icon="shield-xmark">
    Always verify webhook signatures, even in test mode
  </Card>

  <Card title="Not Testing Errors" icon="bug">
    Test error scenarios, not just happy paths. Verify your code checks the `success` field in every response.
  </Card>

  <Card title="Hardcoding Credentials" icon="key">
    Use environment variables for `application_id` and `secret_key`
  </Card>
</CardGroup>

## Need Help?

<Card title="Developer Support" icon="envelope" href="mailto:support@123hub.pro">
  Contact our team if you have questions about testing
</Card>
