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

# Quickstart

> Make your first signed request and complete a sandbox order in about 10 minutes.

This page takes you from sandbox credentials to a completed sandbox order. Every call below runs against sandbox, so no real money moves.

<Info>
  Prefer not to write code? Use the [widget or Client Dashboard](/guides/getting-started/choose-integration) instead. Building with an AI coding agent? Point it at [Build with AI](/guides/getting-started/build-with-ai) first.
</Info>

## Before you start

You need a sandbox **API key** and **API secret**. To get them, email [support@zapyd.com](mailto:support@zapyd.com). Sandbox and production credentials are separate.

## 1. Add the request helper

Every Zapyd request carries three headers: `X-API-KEY`, `X-TIMESTAMP` and `X-SIGNATURE`. The signature is Base64 HMAC-SHA256 over `apiKey|timestamp|canonicalJsonBody`. The helper below handles signing, so later steps only pass a method, a path and a body.

<CodeGroup>
  ```javascript zapyd.js theme={"theme":{"light":"css-variables","dark":"css-variables"}}
  const crypto = require("crypto");

  const HOST = "https://sandbox.zapyd.com";
  const API_KEY = process.env.ZAPYD_API_KEY;
  const API_SECRET = process.env.ZAPYD_API_SECRET;

  // Sorted keys at every level, no whitespace, non-ASCII escaped as \uXXXX
  function canonicalJson(value) {
    if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
    if (value && typeof value === "object") {
      return `{${Object.keys(value).sort()
        .map((k) => `${canonicalJson(k)}:${canonicalJson(value[k])}`)
        .join(",")}}`;
    }
    return JSON.stringify(value).replace(
      /[\u007f-￿]/g,
      (c) => "\\u" + c.charCodeAt(0).toString(16).padStart(4, "0")
    );
  }

  // path includes the module prefix, e.g. "/cms/api/v1/customer/create"
  async function zapyd(method, path, body = {}) {
    const timestamp = Math.floor(Date.now() / 1000).toString();
    const payload = method === "GET" ? "{}" : canonicalJson(body);
    const signature = crypto
      .createHmac("sha256", API_SECRET)
      .update(`${API_KEY}|${timestamp}|${payload}`)
      .digest("base64");

    const res = await fetch(HOST + path, {
      method,
      headers: {
        "Content-Type": "application/json",
        "X-API-KEY": API_KEY,
        "X-TIMESTAMP": timestamp,
        "X-SIGNATURE": signature,
      },
      body: method === "GET" ? undefined : payload,
    });
    const json = await res.json();
    if (!json.status) throw new Error(`${res.status} ${json.err_code}: ${JSON.stringify(json.errors)}`);
    return json.data;
  }

  module.exports = { zapyd };
  ```

  ```python zapyd.py theme={"theme":{"light":"css-variables","dark":"css-variables"}}
  import base64, hashlib, hmac, json, os, time
  import requests

  HOST = "https://sandbox.zapyd.com"
  API_KEY = os.environ["ZAPYD_API_KEY"]
  API_SECRET = os.environ["ZAPYD_API_SECRET"]

  def zapyd(method, path, body=None):
      """path includes the module prefix, e.g. /cms/api/v1/customer/create"""
      timestamp = str(int(time.time()))
      payload = "{}" if method == "GET" else json.dumps(body or {}, sort_keys=True, separators=(",", ":"))
      digest = hmac.new(API_SECRET.encode(), f"{API_KEY}|{timestamp}|{payload}".encode(), hashlib.sha256).digest()

      res = requests.request(
          method,
          HOST + path,
          headers={
              "Content-Type": "application/json",
              "X-API-KEY": API_KEY,
              "X-TIMESTAMP": timestamp,
              "X-SIGNATURE": base64.b64encode(digest).decode(),
          },
          data=None if method == "GET" else payload,
      )
      data = res.json()
      if not data["status"]:
          raise RuntimeError(f'{res.status_code} {data.get("err_code")}: {data.get("errors")}')
      return data["data"]
  ```
</CodeGroup>

<Tip>
  Check your signing against the [test vector](/guides/development-and-testing/authentication#test-vector) before you call the API. If you get `AUTH_INVALID_SIGNATURE`, the test vector shows which part is wrong.
</Tip>

## 2. Create a customer

Each end user is a customer. `client_reference_id` is your own ID for the user. If you send the same value again, Zapyd returns the existing customer, so retrying this call is safe.

<CodeGroup>
  ```javascript Node.js theme={"theme":{"light":"css-variables","dark":"css-variables"}}
  const customer = await zapyd("POST", "/cms/api/v1/customer/create", {
    client_reference_id: "user-001",
    full_name: "Test User",
    email: "test.user@example.com",
    phone: "9911002211",
    alpha_3_country_code: "IND",
  });
  console.log(customer.id, customer.status); // "...", "UNVERIFIED"
  ```

  ```python Python theme={"theme":{"light":"css-variables","dark":"css-variables"}}
  customer = zapyd("POST", "/cms/api/v1/customer/create", {
      "client_reference_id": "user-001",
      "full_name": "Test User",
      "email": "test.user@example.com",
      "phone": "9911002211",
      "alpha_3_country_code": "IND",
  })
  print(customer["id"], customer["status"])  # "...", "UNVERIFIED"
  ```
</CodeGroup>

## 3. Verify the customer

In production, a customer becomes `VERIFIED` through [KYC](/guides/customers/overview). In sandbox, skip KYC and set the status directly with [Mock KYC Status](/api-reference-exchange/endpoint/kyc/mock-kyc-status).

<CodeGroup>
  ```javascript Node.js theme={"theme":{"light":"css-variables","dark":"css-variables"}}
  await zapyd("PATCH", "/cms/api/v1/kyc/mock-kyc-status", {
    customer_id: customer.id,
    kyc_status: "VERIFIED",
  });
  ```

  ```python Python theme={"theme":{"light":"css-variables","dark":"css-variables"}}
  zapyd("PATCH", "/cms/api/v1/kyc/mock-kyc-status", {
      "customer_id": customer["id"],
      "kyc_status": "VERIFIED",
  })
  ```
</CodeGroup>

## 4. Run an order

Pick the direction you want to test. Both flows follow the same pattern: **quote, pay, initiate, track**. A quotation locks the rate and expires at `expiry_time`, so initiate the order before it expires.

<Tabs>
  <Tab title="Payout (crypto to fiat)">
    <Steps>
      <Step title="Add the beneficiary bank account" icon="building-columns">
        ```javascript theme={"theme":{"light":"css-variables","dark":"css-variables"}}
        const bank = await zapyd("POST", "/cms/api/v1/bank/create", {
          customer_id: customer.id,
          bank_account_type: "ACCOUNT_DETAILS",
          identifiers: { account_number: "7627389201", ifsc: "SBIN0001829" },
        });
        ```
      </Step>

      <Step title="Get a quotation" icon="file-invoice-dollar">
        ```javascript theme={"theme":{"light":"css-variables","dark":"css-variables"}}
        const quote = await zapyd("POST", "/pos/api/v1/payout/quotation", {
          customer_id: customer.id,
          bank_id: bank.id,
          asset: "usdt",
          fiat: "inr",
          network: "sepolia",
          payment_method: "IMPS",
          sending_amount: "10", // or receiving_amount, never both
          risk_parameters: {
            ip_address: "203.0.113.10",
            device_id: "device-123",
            suspicious_activity_report: false,
            law_enforcement_agency_report: false,
          },
        });
        // quote.wallet_address, quote.receiving_amount, quote.expiry_time
        ```
      </Step>

      <Step title="Send the crypto" icon="coins">
        Send exactly `quote.sending_amount` of testnet USDT on Sepolia to `quote.wallet_address`. Keep the transaction hash.
      </Step>

      <Step title="Initiate the payout" icon="paper-plane">
        ```javascript theme={"theme":{"light":"css-variables","dark":"css-variables"}}
        const payout = await zapyd("POST", "/pos/api/v1/payout/initiate", {
          quotation_id: quote.id,
          customer_id: customer.id,
          client_reference_id: "payout-001",
          transaction_hash: "0xYOUR_SEPOLIA_TX_HASH",
        });
        console.log(payout.status); // "PROCESSING"
        ```
      </Step>

      <Step title="Complete it in sandbox" icon="flask">
        ```javascript theme={"theme":{"light":"css-variables","dark":"css-variables"}}
        await zapyd("PATCH", "/pos/api/v1/payout/mock-payout-status", {
          payout_id: payout.id,
          payout_status: "SUCCESS", // or FAILED, REFUNDED
        });
        const final = await zapyd("GET", `/pos/api/v1/payout/${payout.id}`);
        ```
      </Step>
    </Steps>
  </Tab>

  <Tab title="Payin (fiat to crypto)">
    The example pays in INR over IMPS. For the US, send `fiat: "usd"` and a US `payment_method` such as `WIRE`. See [Payins](/guides/payments/payins#3-create-a-quotation).

    <Steps>
      <Step title="Get a quotation" icon="file-invoice-dollar">
        ```javascript theme={"theme":{"light":"css-variables","dark":"css-variables"}}
        const quote = await zapyd("POST", "/pis/api/v1/payin/quotation", {
          customer_id: customer.id,
          asset: "usdc",
          fiat: "inr",
          network: "polygon",
          payment_method: "IMPS",
          sending_amount: "10000",
          risk_parameters: {
            ip_address: "203.0.113.10",
            device_id: "device-123",
            suspicious_activity_report: false,
            law_enforcement_agency_report: false,
          },
        });
        // quote.deposit_instructions: where the user sends fiat
        ```
      </Step>

      <Step title="Collect the fiat" icon="money-bill-wave">
        The user transfers exactly `quote.sending_amount` to `quote.deposit_instructions` from a bank account in their own name. Their bank gives them a transfer reference. It's required for INR (the UTR) and optional for USD. In sandbox, use any 12-digit value for INR.
      </Step>

      <Step title="Initiate the payin" icon="arrow-down-to-line">
        ```javascript theme={"theme":{"light":"css-variables","dark":"css-variables"}}
        const payin = await zapyd("POST", "/pis/api/v1/payin/initiate", {
          quotation_id: quote.id,
          customer_id: customer.id,
          client_reference_id: "payin-001",
          transaction_reference_id: "412345678901",
        });
        ```
      </Step>

      <Step title="Complete it in sandbox" icon="flask">
        ```javascript theme={"theme":{"light":"css-variables","dark":"css-variables"}}
        await zapyd("PATCH", "/pis/api/v1/payin/mock-payin-status", {
          payin_id: payin.id,
          payin_status: "SUCCESS", // or ON_HOLD, REFUNDED, FAILED
        });
        const final = await zapyd("GET", `/pis/api/v1/payin/${payin.id}`);
        ```
      </Step>
    </Steps>
  </Tab>
</Tabs>

## 5. Receive webhooks

Don't poll in production. Register an HTTPS endpoint once, and Zapyd sends a signed event whenever a customer, bank, payin or payout changes status.

```javascript theme={"theme":{"light":"css-variables","dark":"css-variables"}}
await zapyd("POST", "/org/api/v1/organizations/api-webhooks", {
  webhook_url: "https://your-server.example.com/webhooks/zapyd",
});
```

Verify each event's signature before you act on it. See [Webhooks](/guides/development-and-testing/webhooks) for the payloads and a verifier.

## Next steps

<CardGroup cols={2}>
  <Card title="Core concepts" icon="shapes" href="/guides/getting-started/core-concepts">
    Objects, statuses, amounts, idempotency and the response envelope.
  </Card>

  <Card title="Payouts" icon="arrow-trend-down" href="/guides/payments/payouts">
    Configuration, rates, fees, EDD and every payout field.
  </Card>

  <Card title="Payins" icon="arrow-trend-up" href="/guides/payments/payins">
    Deposit instructions, transfer references and payin statuses.
  </Card>

  <Card title="Go-live checklist" icon="list-check" href="/guides/development-and-testing/go-live">
    Everything to test before you switch to production keys.
  </Card>
</CardGroup>


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