> ## 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.

# Payins (onramp)

> Turn a user's bank transfer into stablecoins, from quotation to webhook.

A payin turns a user's fiat bank transfer into stablecoins. The user pays Zapyd's collection account, and Zapyd delivers them on the network you choose.

**Base URL:** `https://sandbox.zapyd.com/pis/api/v1`. Customer calls use `/cms/api/v1` and limit calls use `/ren/api/v1`.

## Before you start

* The customer is `VERIFIED`. See [Onboard customers](/guides/customers/overview).
* Your [webhook URL](/guides/development-and-testing/webhooks) is registered.
* Your organization has a delivery wallet for each asset and network you use. Zapyd sets this up during onboarding.

## Flow

```mermaid theme={"theme":{"light":"css-variables","dark":"css-variables"}}
sequenceDiagram
    participant App as Your backend
    participant Z as Zapyd
    participant U as User's bank
    App->>Z: GET /payin/limits/{customer_id}
    App->>Z: POST /payin/quotation
    Z-->>App: rate, fees, deposit_instructions, expiry_time
    App->>U: User transfers the exact amount
    U-->>App: Transfer reference
    App->>Z: POST /payin/initiate (transaction_reference_id)
    Z-->>App: PROCESSING
    Z-->>App: PAYIN webhook: SUCCESS + transaction_hash
```

## 1. Read the configuration

[`GET /payin/configuration`](/api-reference-exchange/endpoint/payin/config/configuration) returns, for each fiat, the supported assets and networks, the crypto minimum and maximum per network and payment method, settlement times, and the risk parameters you must send. Cache it and refresh it daily. The fiats you see depend on the markets enabled for your organization. The example below shows INR.

```json theme={"theme":{"light":"css-variables","dark":"css-variables"}}
{
  "inr": {
    "supported_assets": ["usdt", "usdc"],
    "supported_networks": { "usdt": ["tron", "polygon"], "usdc": ["polygon"] },
    "coin_limits": {
      "usdt": {
        "tron": { "imps": { "min": 28, "max": 5563 }, "upi": { "min": 28, "max": 1113 } }
      }
    },
    "required_risk_parameters": ["ip_address", "device_id", "suspicious_activity_report", "law_enforcement_agency_report"],
    "payment_methods": {
      "imps": { "min_settlement_time": 1, "max_settlement_time": 30 },
      "upi": { "min_settlement_time": 1, "max_settlement_time": 30 }
    }
  }
}
```

## 2. Check the customer's limit

[`GET /ren/api/v1/payin/limits/{customer_id}`](/api-reference-exchange/endpoint/payin/config/edd-limits-\{customer_id}) returns the customer's daily limit, how much is left, and whether they need Enhanced Due Diligence.

| Result | What to do |
| - | - |
| `available_limit` covers the amount | Continue |
| Not enough limit, `is_edd_required: true` | Ask the user to complete [EDD](/guides/payments/limits-and-edd), then check again |
| Not enough limit, `is_edd_required: false` | Block the order until `full_limit_reset_timestamp` |

## 3. Create a quotation

Optionally show a live rate first with [`GET /payin/fetch-rate`](/api-reference-exchange/endpoint/payin/config/fetch-rate). When the user confirms, create a quotation with [`POST /payin/quotation`](/api-reference-exchange/endpoint/payin/quotation/quotation). It locks the rate until `expiry_time`.

The request is the same in every market. `fiat` and `payment_method` pick the market and the rail, and `deposit_instructions` in the response follow that rail.

<Tabs>
  <Tab title="India (INR)">
    ```json Request theme={"theme":{"light":"css-variables","dark":"css-variables"}}
    {
      "customer_id": "8da49e5e-33d1-48b2-b107-eb34f851b2fd",
      "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
      }
    }
    ```

    ```json Response (201) theme={"theme":{"light":"css-variables","dark":"css-variables"}}
    {
      "status": true,
      "message": "Success",
      "data": {
        "id": "da43453c-f854-42ac-9a1e-619b37060bbc",
        "asset": "USDC",
        "fiat": "INR",
        "network": "polygon",
        "payment_method": "IMPS",
        "sending_amount": "10000.00",
        "rate": "91.91",
        "receiving_amount": "108.80",
        "fees": { "zapyd_fee": "0.00", "client_fee_fiat": "0.00", "tds": "0.00" },
        "deposit_instructions": {
          "account_number": "1234567890",
          "ifsc": "YESB0000001",
          "account_name": "Sandbox Technologies Pvt Ltd",
          "deep_link": "upi://pay?pa=testing-payin@ybl&am=10000"
        },
        "expiry_time": "2026-09-30T10:10:00Z"
      }
    }
    ```
  </Tab>

  <Tab title="United States (USD)">
    ```json Request theme={"theme":{"light":"css-variables","dark":"css-variables"}}
    {
      "customer_id": "8da49e5e-33d1-48b2-b107-eb34f851b2fd",
      "asset": "USDC",
      "fiat": "USD",
      "network": "polygon",
      "payment_method": "WIRE",
      "sending_amount": "1000",
      "risk_parameters": {
        "ip_address": "203.0.113.10",
        "device_id": "device-123",
        "suspicious_activity_report": false,
        "law_enforcement_agency_report": false
      }
    }
    ```

    ```json deposit_instructions theme={"theme":{"light":"css-variables","dark":"css-variables"}}
    {
      "account_number": "123456789012",
      "routing_number": "987654321",
      "bank_name": "SANDBOX BANK",
      "bank_address": "1 Sandbox Way, Springfield, IL 62704, USA",
      "account_holder_name": "SANDBOX TECHNOLOGIES PVT LTD",
      "narrative": "SANDBOX-USD-TEST"
    }
    ```

    The rest of the response has the same fields as in India. `ACH_PULL` debits a bank account the customer has already linked, so the user doesn't make a transfer.

    <Note>
      Before an `ACH_PULL` payin, the customer links their bank account: call [`POST /cms/api/v1/bank/generate-link`](/api-reference-exchange/endpoint/bank/generate-link), open the returned `widget_url`, and poll [`GET /cms/api/v1/bank/list/{customer_id}`](/api-reference-exchange/endpoint/bank/list-\{customer_id}) until the account under `accounts` is `VERIFIED`. Send its `id` as `bank_id` in the quotation. See [United States](/guides/country-guides/united-states/overview).
    </Note>
  </Tab>
</Tabs>

| Field | Notes |
| - | - |
| `payment_method` | INR: `IMPS` or `UPI`. USD: `ACH_PULL`, `WIRE` or `RTP` |
| `network` | Required. The network the crypto is delivered on, from `supported_networks` |
| `bank_id` | Required for `ACH_PULL`: the linked account to debit. Optional otherwise: the customer's bank account the money comes from |
| `deposit_instructions` | Where the user pays. The fields follow the rail: account and IFSC for IMPS, a `deep_link` for UPI (see [UPI QR codes](/guides/country-guides/india/qr-integration)), account and routing number for US transfers |

## 4. Collect the fiat

Show the user `deposit_instructions`, the exact `sending_amount` and the time left before `expiry_time`.

<Warning>
  Payins are rejected or refunded when the user pays from an account that isn't in their own KYC name, pays a different amount, or splits the payment. Tell the user this before they pay.
</Warning>

After the transfer, the user's bank gives them a reference. Send it as `transaction_reference_id` when you initiate:

| Market | Reference | Required |
| - | - | - |
| India (IMPS, UPI) | 12- or 16-digit UTR. See [Understanding UTR numbers](/guides/country-guides/india/understanding-utr) | Yes |
| United States | The bank's transfer reference, if the user has one | No |

## 5. Initiate the payin

Call [`POST /payin/initiate`](/api-reference-exchange/endpoint/payin/order/initiate) before the quotation expires.

```json theme={"theme":{"light":"css-variables","dark":"css-variables"}}
{
  "quotation_id": "da43453c-f854-42ac-9a1e-619b37060bbc",
  "customer_id": "8da49e5e-33d1-48b2-b107-eb34f851b2fd",
  "client_reference_id": "payin-001",
  "transaction_reference_id": "412345678901"
}
```

`transaction_reference_id` can't be reused across payins. It's required for INR and optional for other fiats. The payin starts as `PROCESSING`.

## 6. Track the result

Zapyd matches the transfer, then delivers the crypto and sends a `PAYIN` webhook.

| Event | Meaning | Your action |
| - | - | - |
| `SUCCESS` | Fiat received and crypto delivered | Credit the user. Store `metadata.transaction_hash` |
| `FAILED` | The transfer couldn't be matched, for example `INCORRECT_UTR` or `PAYMENT_NOT_RECEIVED` | Don't credit. Show the reason |
| `REFUND_INITIATED`, `REFUNDED` | Zapyd is returning the fiat, for example `THIRD_PARTY_PAYMENT` or `INCORRECT_AMOUNT` | Don't credit. Tell the user the money is coming back |
| `ON_HOLD` | Held for review | Don't credit. Contact support with the payin `id` |

To poll instead, call [`GET /payin/{payin_id}`](/api-reference-exchange/endpoint/payin/order/\{payin_id}) no more than once a minute. [`GET /payin/history`](/api-reference-exchange/endpoint/payin/order/history) lists orders with filters.

## Where the crypto goes

Zapyd delivers the crypto to your organization's delivery wallet for that asset and network, set up during onboarding. The address is `wallet_address` on the payin. If no wallet is set up for the pair, the quotation fails with `Not configured for asset: X & network: Y`. Credit the user from that wallet in your own ledger.

## Test it in sandbox

1. Create a quotation, then initiate it. For INR, use any 12-digit `transaction_reference_id`. For USD you can leave it out.
2. Move the payin with [Mock Payin Status](/api-reference-exchange/endpoint/payin/order/mock-payin-status): `SUCCESS`, `ON_HOLD`, `REFUNDED` or `FAILED`.
3. Check that your webhook handler credits the user only on `SUCCESS`.


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