Skip to main content
This guide takes you from credentials to a completed deposit. Examples use Node.js 18+; the Signature Generation Guide has the same helper in C# and Python.
1

Get your credentials

Ask C2C for a merchant API key (mk_live_...) and signing secret (sksec_...) for the development environment. Both are shown only once. Store them as environment variables:
Optionally, set a default callback URL for webhooks in Merchant Portal → Settings.
2

Add the API helper

Every request carries your API key in the C2C-API-Key header. Deposit and withdrawal requests also carry a signature field in the body; C2C rejects them without one. Status and balance requests aren’t signed.
c2c.js
3

Test your credentials

Read your wallet balance. It’s a safe call with no side effects:
A 401 here means the API key is wrong. Check your signing code separately against the worked examples: on order creation, a 401 with a code means the signature is wrong. See troubleshooting.
4

Create a deposit order

Response (200)
Store orderId. If the call times out, retry with the same Idempotency-Key and the same body. You will get the same order back, never a duplicate. You can also look the order up by its merchantOrderRef.If no member is available right now, the order is still accepted with status Awaiting. Redirect the customer as usual: a member is assigned when they open the payment page.
5

Redirect the customer

Send the customer to paymentPageUrl within 15 minutes. The page opens on the walletType you requested. With JazzCash, as here, they pay the displayed account number and enter the last digits of their transaction ID. Other payment methods ask for something else: on the QR methods the customer gives the account they pay from instead of a transaction ID. See Hosted payment page.
6

Get the outcome

Handle the order.completed / order.failed webhook, and confirm with a status query:
Poll until the status is final if you don’t use webhooks. Every final outcome sends a webhook, but an order in ManualReview doesn’t until C2C resolves it. See Order status.

Withdrawals

Paying a customer out uses the same endpoint, with the customer’s account in the request. No redirect is needed:
Keep your merchant wallet funded. A withdrawal is accepted only if your available balance covers its amount + fee, otherwise 422 Insufficient merchant balance. Once accepted, its amount + fee is reserved (taken out of your available balance) until the member confirms the payout, when it is debited. If no member is available, the withdrawal is accepted as Awaiting and assigned automatically once one is; its amount + fee stays reserved meanwhile. See the Withdrawal flow.

Go-live checklist

  • API key and signing secret stored in a secret manager, never in client code or logs
  • Every deposit and withdrawal request signed. Your code reproduces the worked examples.
  • Webhook signatures verified with a constant-time comparison
  • Idempotency-Key persisted before the first attempt and reused on every retry
  • 5xx and network errors retried with backoff, resending the same body with the same Idempotency-Key
  • Reconciliation job polls orders that haven’t reached a final status
  • Fulfil and mark payouts paid only on Completed
  • Awaiting and ManualReview treated as pending, never as failed
  • 409 handled: one live order per merchantOrderRef
  • Wallet balance monitored before submitting withdrawals; 422 Insufficient merchant balance handled
  • Customers returned to your site through successUrl / failureUrl, with the outcome confirmed server-side
  • Production base URL, API key and signing secret obtained from C2C
  • Production callback URL configured (HTTPS)