<!-- ZenPays documentation · https://docs.zenpayz.com/docs/rest-api/endpoints/ramp/create-ramp-intent -->

# Create Ramp Intent

Create a standalone ramp intent for on-ramp (buy crypto) or off-ramp (sell crypto). A ramp intent represents a merchant-initiated ramp session that tracks the full lifecycle of a transaction — from creation through to completion or expiry.

<EndpointHeader verb="POST" path="/payment/api/v1/ramp-intents" />

## Request

### Headers

<ParamTable
  label="Header"
  rows={[
    { name: "Content-Type", required: true, desc: "`application/json`" },
    { name: "Authorization", required: true, desc: "`Bearer {api_key}`" },
    { name: "x-merchant-id", required: true, desc: "Your merchant ID" },
    { name: "x-request-id", required: true, desc: "Unique request ID for tracing" },
  ]}
/>

:::caution Authenticated Endpoint
This endpoint requires API key authentication. Include your API key in the `Authorization` header and your merchant ID in `x-merchant-id`.
:::

### Body Parameters

<ParamTable
  rows={[
    { name: "userId", type: "string", required: true, desc: "Unique user identifier from your system. Used as the KYC record key." },
    { name: "kycVerified", type: "boolean", required: true, desc: "Whether you have already verified this user's identity. When `true`, ZenPays KYC verification is skipped in the widget." },
    { name: "type", type: "string", required: true, desc: "`buy` (on-ramp) or `sell` (off-ramp)" },
    { name: "wallets", type: "array", required: true, desc: "Array of `{ network, address }` objects (min 1)" },
    { name: "fiatCurrency", type: "string", required: true, desc: "Fiat currency code (e.g. `USD`, `EUR`)" },
    { name: "cryptoCurrency", type: "string", desc: "Crypto identifier (e.g. `btc_bitcoin`, `usdt_tron`)" },
    { name: "amount", type: "number", desc: "Default amount (must be positive)" },
    { name: "successUrl", type: "string", desc: "URL to redirect on success" },
    { name: "cancelUrl", type: "string", desc: "URL to redirect on cancel" },
    { name: "customerEmail", type: "string", desc: "Customer's email address" },
    { name: "metadata", type: "object", desc: "Custom key-value pairs for your reference" },
  ]}
/>

:::tip KYC Behavior
When `kycVerified: true`, the checkout widget skips ZenPays KYC verification entirely — use this when your platform has already verified the user's identity.

When `kycVerified: false` (default), the widget checks the user's KYC status. If unverified, the customer is prompted to complete identity verification before proceeding.
:::

### Wallet Object

<ParamTable
  rows={[
    { name: "network", type: "string", required: true, desc: "Blockchain network (e.g. `ethereum`, `tron`, `solana`)" },
    { name: "address", type: "string", required: true, desc: "Wallet address on the network" },
  ]}
/>

### Sample Request Body

```json
{
  "userId": "usr_abc123",
  "kycVerified": false,
  "type": "buy",
  "wallets": [
    {
      "network": "ethereum",
      "address": "0x742d35Cc6634C0532925a3b844Bc9e7595f2bD28"
    }
  ],
  "fiatCurrency": "USD",
  "cryptoCurrency": "eth_ethereum",
  "amount": 100,
  "successUrl": "https://yourapp.com/success",
  "cancelUrl": "https://yourapp.com/cancel",
  "customerEmail": "customer@example.com",
  "metadata": {
    "orderId": "order_123"
  }
}
```

## Response

### Success (201 Created)

```json
{
  "success": true,
  "data": {
    "intentId": "ri_1710345678000_a1b2c3d4e5f6g7h8",
    "redirectUrl": "https://checkout.zenpayz.com/payments/ramping/widget/ri_1710345678000_a1b2c3d4e5f6g7h8",
    "expiresAt": "2026-03-15T10:00:00.000Z"
  },
  "message": "Ramp intent created"
}
```

### Response Fields

<ParamTable
  rows={[
    { name: "intentId", type: "string", desc: "Unique intent identifier (format: `ri_{timestamp}_{hex}`)" },
    { name: "redirectUrl", type: "string", desc: "URL to redirect the customer to the ramp widget" },
    { name: "expiresAt", type: "string", desc: "ISO 8601 expiry timestamp (1 hour from creation)" },
  ]}
/>

:::info Intent Expiry
Ramp intents automatically expire **1 hour** after creation. Once expired, the intent cannot be used and a new one must be created.
:::

### Error Responses

<ErrorTable
  rows={[
    { code: "VALIDATION_ERROR", status: "400", message: "Invalid request body or missing required fields" },
    { code: "RAMP_INTENT_CREATION_FAILED", status: "500", message: "Failed to create the ramp intent" },
  ]}
/>

<CodeRail>

## Examples

<Tabs groupId="language">
  <TabItem value="curl" label="cURL" default>

```bash
curl -X POST https://api.zenpayz.com/payment/api/v1/ramp-intents \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer zp_test_your_api_key" \
  -H "x-merchant-id: merch_abc123" \
  -H "x-request-id: req_$(date +%s)" \
  -d '{
    "userId": "usr_abc123",
    "kycVerified": false,
    "type": "buy",
    "wallets": [
      { "network": "ethereum", "address": "0x742d35Cc6634C0532925a3b844Bc9e7595f2bD28" }
    ],
    "fiatCurrency": "USD",
    "cryptoCurrency": "eth_ethereum",
    "amount": 100,
    "customerEmail": "customer@example.com",
    "successUrl": "https://yourapp.com/success",
    "cancelUrl": "https://yourapp.com/cancel"
  }'
```

  </TabItem>
  <TabItem value="javascript" label="JavaScript">

```javascript
const response = await fetch('https://api.zenpayz.com/payment/api/v1/ramp-intents', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer zp_test_your_api_key',
    'x-merchant-id': 'merch_abc123',
    'x-request-id': `req_${Date.now()}`,
  },
  body: JSON.stringify({
    userId: 'usr_abc123',
    kycVerified: false,
    type: 'buy',
    wallets: [
      { network: 'ethereum', address: '0x742d35Cc6634C0532925a3b844Bc9e7595f2bD28' },
    ],
    fiatCurrency: 'USD',
    cryptoCurrency: 'eth_ethereum',
    amount: 100,
    customerEmail: 'customer@example.com',
    successUrl: 'https://yourapp.com/success',
    cancelUrl: 'https://yourapp.com/cancel',
  }),
});

const { data } = await response.json();

console.log(`Intent ID: ${data.intentId}`);
console.log(`Redirect to: ${data.redirectUrl}`);
console.log(`Expires at: ${data.expiresAt}`);

// Redirect the customer to the ramp widget
window.location.href = data.redirectUrl;
```

  </TabItem>
  <TabItem value="python" label="Python">

```python
import requests
import time

response = requests.post(
    "https://api.zenpayz.com/payment/api/v1/ramp-intents",
    headers={
        "Authorization": "Bearer zp_test_your_api_key",
        "x-merchant-id": "merch_abc123",
        "x-request-id": f"req_{int(time.time())}",
    },
    json={
        "userId": "usr_abc123",
        "kycVerified": False,
        "type": "buy",
        "wallets": [
            {"network": "ethereum", "address": "0x742d35Cc6634C0532925a3b844Bc9e7595f2bD28"}
        ],
        "fiatCurrency": "USD",
        "cryptoCurrency": "eth_ethereum",
        "amount": 100,
        "customerEmail": "customer@example.com",
        "successUrl": "https://yourapp.com/success",
        "cancelUrl": "https://yourapp.com/cancel",
    },
)

data = response.json()["data"]
print(f"Intent ID: {data['intentId']}")
print(f"Redirect to: {data['redirectUrl']}")
print(f"Expires at: {data['expiresAt']}")
```

  </TabItem>
</Tabs>

</CodeRail>

## Intent Status Lifecycle

```
                    ┌──────────┐
                    │ created  │ ← Initial state
                    └────┬─────┘
                         │
                    First GET request
                         │
                    ┌────▼─────┐
               ┌────│  active  │────┐
               │    └────┬─────┘    │
               │         │          │
          ┌────▼────┐ ┌──▼───────┐ ┌▼──────────┐
          │cancelled│ │completed │ │  expired   │
          └─────────┘ └──────────┘ └────────────┘
                   (terminal states)
```

## Webhook Events

When a ramp intent reaches a terminal state, ZenPay delivers a webhook to your configured endpoint. Configure webhooks in the merchant dashboard under **Developer Tools → Webhooks**.

### Events

| Event | When | Description |
|-------|------|-------------|
| `ramp.completed` | Buy: crypto sent to wallet. Sell: fiat payout submitted. | The ramp flow finished successfully. |
| `ramp.failed` | Crypto send or payout submission failed. | The ramp flow encountered an error. |

### Buy Ramp Completed (`ramp.completed`)

Delivered when crypto has been sent to the customer's wallet address.

```json
{
  "event_type": "ramp.completed",
  "payment_data": {
    "merchant_id": "m_abc123",
    "intentId": "ri_1710345678000_a1b2c3d4e5f6g7h8",
    "type": "buy",
    "status": "completed",
    "fiatCurrency": "USD",
    "cryptoCurrency": "eth_ethereum",
    "amount": 100,
    "chain": "ethereum",
    "destinationAddress": "0x742d35Cc6634C0532925a3b844Bc9e7595f2bD28",
    "fireblocksTxId": "fb_tx_9a8b7c6d5e4f",
    "completedAt": "2026-03-16T14:30:00.000Z"
  }
}
```

### Sell Ramp Completed (`ramp.completed`)

Delivered when the fiat payout has been submitted to the customer's bank account.

```json
{
  "event_type": "ramp.completed",
  "payment_data": {
    "merchant_id": "m_abc123",
    "intentId": "ri_1710345678000_a1b2c3d4e5f6g7h8",
    "type": "sell",
    "status": "completed",
    "fiatCurrency": "USD",
    "cryptoCurrency": "usdt_tron",
    "amount": 20,
    "payoutId": "po_xyz789",
    "payoutAmount": 19.50,
    "payoutCurrency": "USD",
    "completedAt": "2026-03-16T14:30:00.000Z"
  }
}
```

### Ramp Failed (`ramp.failed`)

Delivered when the crypto send (buy) or payout submission (sell) fails.

```json
{
  "event_type": "ramp.failed",
  "payment_data": {
    "merchant_id": "m_abc123",
    "intentId": "ri_1710345678000_a1b2c3d4e5f6g7h8",
    "type": "buy",
    "status": "failed",
    "fiatCurrency": "USD",
    "cryptoCurrency": "eth_ethereum",
    "amount": 100,
    "reason": "Crypto send failed",
    "failedAt": "2026-03-16T14:30:00.000Z"
  }
}
```

### Webhook Payload Fields

<ParamTable
  rows={[
    { name: "merchant_id", type: "string", desc: "Your merchant ID" },
    { name: "intentId", type: "string", desc: "Ramp intent ID (`ri_...`)" },
    { name: "type", type: "string", desc: "`\"buy\"` or `\"sell\"`" },
    { name: "status", type: "string", desc: "`\"completed\"` or `\"failed\"`" },
    { name: "fiatCurrency", type: "string", desc: "ISO currency code (e.g. `USD`, `EUR`)" },
    { name: "cryptoCurrency", type: "string", desc: "Crypto identifier (e.g. `eth_ethereum`, `usdt_tron`)" },
    { name: "amount", type: "number", desc: "Original intent amount" },
    { name: "completedAt", type: "string", desc: "ISO 8601 completion timestamp" },
    { name: "failedAt", type: "string", desc: "ISO 8601 failure timestamp" },
    { name: "reason", type: "string", desc: "Human-readable failure reason" },
    { name: "chain", type: "string", desc: "Blockchain network (e.g. `ethereum`, `tron`)" },
    { name: "destinationAddress", type: "string", desc: "Wallet address crypto was sent to" },
    { name: "cryptoTxId", type: "string", desc: "Crypto transaction reference" },
    { name: "payoutId", type: "string", desc: "Payout reference ID" },
    { name: "payoutAmount", type: "number", desc: "Fiat amount paid out (after fees)" },
    { name: "payoutCurrency", type: "string", desc: "Fiat currency of payout" },
  ]}
/>

### Webhook Headers

| Header | Description |
|--------|-------------|
| `X-ZenPay-Signature` | HMAC-SHA256 signature of the request body |
| `X-Zenpay-Event` | Event type (e.g. `ramp.completed`) |
| `X-Zenpay-Event-Id` | Unique delivery ID for deduplication |
| `X-Zenpay-Timestamp` | ISO 8601 timestamp of delivery |
| `X-Zenpay-Attempt` | Delivery attempt number (1, 2, or 3) |

See the [Webhooks guide](/docs/guides/webhooks) for signature verification, retry behavior, and best practices.

## Next Steps

- [Get Ramp Intent](/docs/rest-api/endpoints/ramp/get-ramp-intent) — Retrieve intent details and widget URL
- [List Ramp Intents](/docs/rest-api/endpoints/ramp/list-ramp-intents) — Browse intent history
- [On-Ramp Flow](/docs/examples/on-ramp-flow) — Complete buy integration guide
- [Off-Ramp Flow](/docs/examples/off-ramp-flow) — Complete sell integration guide
