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

# Quickstart

> Create and track your first signed deposit order in a few steps.

This guide takes you from credentials to a completed deposit. Examples use Node.js 18+; the [Signature Generation Guide](/api-reference/signature-guide) has the same helper in C# and Python.

<Steps>
  <Step title="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:

    ```bash theme={null}
    export C2C_BASE_URL="https://api.c2cpayment.net"
    export C2C_API_KEY="mk_live_..."
    export C2C_SIGNING_SECRET="sksec_..."
    ```

    Optionally, set a default callback URL for [webhooks](/guides/webhooks) in **Merchant Portal → Settings**.
  </Step>

  <Step title="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.

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

    const BASE_URL = process.env.C2C_BASE_URL;

    function signBody(payload, signingSecret) {
      const params = Object.entries(payload)
        .filter(([key, value]) =>
          key.toLowerCase() !== 'signature' &&
          value !== null && value !== undefined && value !== '' &&
          typeof value !== 'object')                                  // objects and arrays are not signed
        .map(([key, value]) => [key.toLowerCase(), String(value)])
        .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));           // ASCII order of the lowercase keys

      const queryString = params.map(([key, value]) => `${key}=${value}`).join('&');
      return crypto.createHash('md5').update(queryString + signingSecret, 'utf8').digest('hex');
    }

    async function c2c(method, path, payload, extraHeaders = {}) {
      const body = payload === undefined
        ? undefined
        : JSON.stringify({ ...payload, signature: signBody(payload, process.env.C2C_SIGNING_SECRET) });
      const response = await fetch(BASE_URL + path, {
        method,
        headers: {
          'C2C-API-Key': process.env.C2C_API_KEY,
          ...(body ? { 'Content-Type': 'application/json' } : {}),
          ...extraHeaders,
        },
        body,
      });
      const text = await response.text();
      return { status: response.status, body: text ? JSON.parse(text) : null };
    }

    module.exports = { c2c, signBody };
    ```
  </Step>

  <Step title="Test your credentials">
    Read your wallet balance. It's a safe call with no side effects:

    ```javascript theme={null}
    const { c2c } = require('./c2c');
    console.log(await c2c('GET', '/api/merchant/orders/balance'));
    // { status: 200, body: { data: { availableBalance: 0.0, reservedBalance: 0.0, frozenBalance: 0.0, currency: 'PKR' } } }
    ```

    A `401` here means the API key is wrong. Check your signing code separately against the [worked examples](/api-reference/signature-guide#worked-examples): on order creation, a `401` with a `code` means the signature is wrong. See [troubleshooting](/api-reference/signature-guide#common-mistakes-and-troubleshooting).
  </Step>

  <Step title="Create a deposit order">
    ```javascript theme={null}
    const idempotencyKey = 'INV-10045';   // persist this with your order first

    const { status, body } = await c2c('POST', '/api/merchant/orders', {
      orderType: 'Deposit',
      amount: 1500,
      currency: 'PKR',
      walletType: 'JazzCash',
      merchantOrderRef: 'INV-10045',
      customerReference: 'player_12345',
      callbackUrl: 'https://merchant.example.com/webhooks/c2c',
    }, { 'Idempotency-Key': idempotencyKey });

    console.log(status, body);
    ```

    ```json Response (200) theme={null}
    {
      "data": {
        "orderId": "788fcae0-5214-403c-baa2-f318c0eb1b5b",
        "paymentPageUrl": "https://<payment-page-host>/pay?order_id=788fcae0-...&expires=...&signature=...",
        "status": "Queued",
        "orderReference": "ORD-20261008120620-3f6b",
        "expiresAt": "2026-10-08T12:21:20.605646Z",
        "stage": "Assigned",
        "merchantOrderRef": "INV-10045"
      }
    }
    ```

    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.
  </Step>

  <Step title="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](/guides/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](/guides/hosted-payment-page).
  </Step>

  <Step title="Get the outcome">
    Handle the `order.completed` / `order.failed` [webhook](/guides/webhooks), and confirm with a status query:

    ```javascript theme={null}
    const { body: { data } } = await c2c('GET', `/api/merchant/orders/${orderId}/status`);
    if (data.status === 'Completed') {
      // fulfil the purchase
    }
    ```

    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](/guides/order-status).
  </Step>
</Steps>

## Withdrawals

Paying a customer out uses the same endpoint, with the customer's account in the request. No redirect is needed:

```javascript theme={null}
await c2c('POST', '/api/merchant/orders', {
  orderType: 'Withdrawal',
  amount: 2000,
  currency: 'PKR',
  walletType: 'Easypaisa',
  customerAccountNumber: '03001234567',
  merchantOrderRef: 'PAYOUT-20017',
  callbackUrl: 'https://merchant.example.com/webhooks/c2c',
}, { 'Idempotency-Key': 'PAYOUT-20017' });
```

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](/guides/withdrawal-flow).

## Go-live checklist

<AccordionGroup>
  <Accordion title="Security">
    * 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](/api-reference/signature-guide#worked-examples).
    * Webhook signatures verified with a constant-time comparison
  </Accordion>

  <Accordion title="Reliability">
    * `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
  </Accordion>

  <Accordion title="Business logic">
    * 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
  </Accordion>

  <Accordion title="Production">
    * Production base URL, API key and signing secret obtained from C2C
    * Production callback URL configured (HTTPS)
  </Accordion>
</AccordionGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.