Developer Documentation
Accept fiat payments with instant USDC settlements. RESTful API with sandbox testing, webhooks, and multi-currency support.
https://chain2pay.is/api/v2Authentication
All API v2 endpoints require an API key. Generate keys from your Dashboard under the API section.
API Keys
Pass your key via the Authorization header or x-api-key header.
c2p_live_Production payments - real money processing
c2p_sandbox_Test payments - no real transactions
| Key Type | Prefix | Purpose |
|---|---|---|
| LIVE | c2p_live_ | Production payments - real money processing |
| SANDBOX | c2p_sandbox_ | Test payments - no real transactions, confirmable via API |
Authorization: Bearer c2p_live_a1b2c3d4e5f6...Note: Your API key is linked to a verified wallet. The merchant wallet is automatically resolved - you don't need to send it in requests.
CORS: The API is fully CORS-enabled. You can call it directly from any origin — including file:// HTML pages, localhost, and any domain — as long as you pass your Bearer key in the Authorization header. No allowlist, no preflight headaches.
Rate limits:
- 300 requests / minute / API key on
/api/v2/*(5 req/sec) - 1,200 requests / minute / IP globally (30-second block after overflow)
When exceeded, the API responds with 429 Too Many Requests and a Retry-After header (in seconds).
Create Payment
Generate a hosted payment page. Redirect your customer to the checkout_url to complete the transaction.
/api/v2/paymentsRequest Body
amountPayment amount in fiat (e.g. 49.99). Minimum varies per provider, maximum 10000.
currencyFiat currency: USD, EUR, GBP, or CAD. Default: USD.
callback_urlYour server's webhook endpoint for payment notifications.
customer_emailCustomer email for receipt delivery.
providerForce a specific gateway. Default: multihosted (auto-routing).
metadataArbitrary key-value pairs for your own reference.
| Parameter | Type | Required | Description |
|---|---|---|---|
| amount | Number | Yes | Payment amount in fiat (e.g. 49.99). Minimum varies per provider, maximum 10000. |
| currency | String | Optional | Fiat currency: USD, EUR, GBP, or CAD. Default: USD. |
| callback_url | String | Optional | Your server's webhook endpoint for payment notifications. |
| customer_email | String | Optional | Customer email for receipt delivery. |
| provider | String | Optional | Force a specific gateway. Default: multihosted (auto-routing). |
| metadata | Object | Optional | Arbitrary key-value pairs for your own reference (e.g. {"order_ref": "ORD-123"}). |
Code Examples
curl -X POST https://chain2pay.is/api/v2/payments \
-H "Authorization: Bearer c2p_sandbox_a1b2c3d4e5f6..." \
-H "Content-Type: application/json" \
-d '{
"amount": 55.00,
"currency": "EUR",
"callback_url": "https://yoursite.com/webhooks/c2p",
"customer_email": "[email protected]"
}'Response
{
"id": "C2P-1741380553-a7f3c9e1b2d4f6a8",
"object": "payment",
"amount": 55.00,
"currency": "EUR",
"status": "pending",
"sandbox": false,
"checkout_url": "https://checkout.chain2pay.is/c/e3c3da6f06e89d65a7eb",
"provider": "multihosted",
"merchant_wallet": "0x606Ea6BfA08f0E5C390Ef3bBCcF620f46EC673C8",
"callback_url": "https://yoursite.com/webhooks/c2p",
"metadata": null,
"created_at": "2026-04-07T18:00:00.000Z"
}Redirect your customer to the checkout_url to complete the transaction. The merchant_wallet is automatically resolved from your API key.
Get Payment
Retrieve the current status and details of a payment.
/api/v2/payments/:idParameters
idThe payment ID returned from POST /api/v2/payments.
curl https://chain2pay.is/api/v2/payments/C2P-1741380553-a7f3c9e1b2d4f6a8 \
-H "Authorization: Bearer c2p_sandbox_a1b2c3d4e5f6..."Response
{
"id": "C2P-1741380553-a7f3c9e1b2d4f6a8",
"object": "payment",
"amount": 55.00,
"currency": "EUR",
"status": "paid",
"sandbox": false,
"checkout_url": "https://checkout.chain2pay.is/c/e3c3da6f06e89d65a7eb",
"provider": "multihosted",
"merchant_wallet": "0x606Ea6B...",
"callback_url": "https://yoursite.com/webhooks/c2p",
"tx_hash": "0xb4f7b8a3c2...",
"paid_amount": 55.00,
"paid_coin": "polygon_usdc",
"metadata": null,
"created_at": "2026-04-07T18:00:00.000Z"
}Status values: pending, processing, paid, expired, dispatch_failed
processing is a rare transient state: it appears when funds arrive on the temporary wallet after an order was already marked expired (late blockchain confirmation, RPC hiccup, safety-check recovery). The order is automatically re-opened and settled as usual.
Pending payments are kept alive for 7 days. After that, a cron running every 5 minutes marks them expired if the temporary wallet is still empty.
Sandbox Mode
Use sandbox API keys (c2p_sandbox_*) to create test payments. Sandbox payments are identical to live payments but don't process real money.
How it works: Create a payment with your sandbox key. The payment appears in your dashboard as a test transaction. Confirm it via the API or the dashboard's "Send Test Callback" button.
1. Create a sandbox payment
Use the same POST /api/v2/payments endpoint with your sandbox key.
curl -X POST https://chain2pay.is/api/v2/payments \
-H "Authorization: Bearer c2p_sandbox_a1b2c3d4e5f6..." \
-H "Content-Type: application/json" \
-d '{"amount": 10.00, "currency": "USD", "callback_url": "https://yoursite.com/webhooks/c2p"}'2. Confirm the payment
/api/v2/payments/:id/sandbox-confirmSimulates a successful payment. The callback is sent to your callback_url with sandbox=true and txid_out=SANDBOX_TEST.
curl -X POST https://chain2pay.is/api/v2/payments/C2P-xxx/sandbox-confirm \
-H "Authorization: Bearer c2p_sandbox_a1b2c3d4e5f6..."Sandbox Confirm Response
{
"id": "C2P-1741380553-a7f3c9e1b2d4f6a8",
"object": "payment",
"status": "paid",
"sandbox": true,
"callback_sent": true,
"callback_status": 200
}You can also confirm sandbox payments from your Dashboard → Transactions → click a sandbox order → "Send Test Callback".
Webhooks
When a payment succeeds, Chain2Pay sends a GET request to your callback_url with the following query parameters.
Webhook Parameters
chain2pay_order_idUnique payment ID (e.g. C2P-...).
txid_outPolygon transaction hash - proof of USDC transfer. SANDBOX_TEST for sandbox.
value_coinAmount paid in USDC.
coinPayout cryptocurrency (e.g. polygon_usdc or currency code).
providerPayment processor used (e.g. revolut, unlimit).
sandboxtrue for test payments, absent for live.
customer_emailBuyer's email (if provided at creation).
| Parameter | Description |
|---|---|
| chain2pay_order_id | Unique payment ID (e.g. C2P-...). |
| txid_out | Polygon transaction hash - proof of USDC transfer. SANDBOX_TEST for sandbox. |
| value_coin | Amount paid in USDC. |
| coin | Payout cryptocurrency (e.g. polygon_usdc or currency code). |
| provider | Payment processor used (e.g. revolut, unlimit). |
| sandbox | true for test payments, absent for live. |
| customer_email | Buyer's email (if provided at creation). |
Example Webhook Call
GET https://yoursite.com/webhooks/c2p
?chain2pay_order_id=C2P-1741380553-a7f3c9e1b2d4f6a8
&txid_out=0xb4f7b8a3c2...
&value_coin=55.00
&coin=polygon_usdc
&provider=revolut
&[email protected]Signature (optional)
Generate a webhook secret key from Settings → Webhooks in your dashboard. Once a secret is set, every callback we deliver includes an x-chain2pay-signature header whose value is the lowercase hex HMAC-SHA256 of the full request URL (including the query string) using that secret. Verify it on your server to ensure the webhook is genuinely from Chain2Pay. The dashboard also exposes a Send test webhook button that fires a signed GET at any URL so you can dry-run your verification handler before going live.
const crypto = require('crypto');
function verifyChain2PaySignature(fullUrl, header, secret) {
const expected = crypto.createHmac('sha256', secret).update(fullUrl).digest('hex');
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(header || ''));
}Delivery & Retries
Chain2Pay retries failed deliveries up to 4 times with exponential backoff (1s, 2s, 4s). After a successful 2xx response the webhook is marked as delivered and will not be re-sent. A hard cap of 10 total attempts across all triggers prevents retry loops. TLS errors (expired or invalid certificates) stop retries immediately.
Security tip: Always verify the chain2pay_order_id against your database (and if configured, the x-chain2pay-signature header) before fulfilling the order. Treat the webhook as a hint — never the source of truth.
Providers & Limits
Fetch the list of active payment providers and their minimum transaction limits.
The status field reflects live provider health. It is either "active" (provider currently accepts buyers) or "maintenance" (the provider has been flagged DOWN by Chain2Pay's health monitor — buyer-facing pages display a maintenance banner and the provider is excluded from MultiHosted routing). Avoid forcing a maintenance provider via the provider parameter on /api/v2/intents until it recovers; otherwise checkout will fall back to the MultiHosted chooser at runtime.
/api/v2/providerscurl https://chain2pay.is/api/v2/providers \
-H "Authorization: Bearer c2p_sandbox_a1b2c3d4e5f6..."Response
{
"object": "list",
"data": [
{ "id": "multihosted", "name": "MultiHosted Aggregator", "status": "active", "minimum_amount": 6, "minimum_currency": "USD", "currency_specific": null },
{ "id": "revolut", "name": "Revolut (EU/EEA)", "status": "active", "minimum_amount": 6, "minimum_currency": "USD", "currency_specific": { "USD": 6, "EUR": 6, "GBP": 6, "CAD": 10 } },
{ "id": "unlimit", "name": "Unlimit (Global)", "status": "maintenance", "minimum_amount": 12, "minimum_currency": "USD", "currency_specific": { "USD": 12, "EUR": 11, "CAD": 17 } },
{ "id": "binance", "name": "Binance Connect", "status": "active", "minimum_amount": 15, "minimum_currency": "USD", "currency_specific": null },
{ "id": "transak", "name": "Transak", "status": "active", "minimum_amount": 6, "minimum_currency": "USD", "currency_specific": { "USD": 6, "EUR": 6, "GBP": 6, "CAD": 8 } },
{ "id": "stripe", "name": "Stripe", "status": "active", "minimum_amount": 3, "minimum_currency": "USD", "currency_specific": null },
{ "id": "robinhood", "name": "Robinhood", "status": "active", "minimum_amount": 5, "minimum_currency": "USD", "currency_specific": null },
{ "id": "moonpay", "name": "MoonPay", "status": "active", "minimum_amount": 20, "minimum_currency": "USD", "currency_specific": null },
{ "id": "paypal", "name": "PayPal", "status": "active", "minimum_amount": 5, "minimum_currency": "USD", "currency_specific": { "USD": 5, "EUR": 5, "GBP": 5, "CAD": 5 } },
{ "id": "blockchain", "name": "Blockchain.com", "status": "active", "minimum_amount": 15, "minimum_currency": "USD", "currency_specific": { "USD": 15, "EUR": 15, "GBP": 15, "CAD": 20 } },
{ "id": "cryptocom", "name": "Crypto.com", "status": "active", "minimum_amount": 5, "minimum_currency": "USD", "currency_specific": { "USD": 5, "EUR": 5, "GBP": 5, "CAD": 5 } },
{ "id": "coinbase", "name": "Coinbase", "status": "active", "minimum_amount": 10, "minimum_currency": "USD", "currency_specific": { "USD": 10, "EUR": 10, "GBP": 10, "CAD": 10 } },
{ "id": "alchemy", "name": "Alchemy Pay", "status": "active", "minimum_amount": 5, "minimum_currency": "USD", "currency_specific": { "USD": 5, "EUR": 5, "GBP": 5, "CAD": 8 } },
{ "id": "kryptonim", "name": "Kryptonim", "status": "active", "minimum_amount": 5, "minimum_currency": "USD", "currency_specific": { "USD": 5, "EUR": 5, "GBP": 5, "CAD": 7 } },
{ "id": "banxa", "name": "Banxa", "status": "active", "minimum_amount": 10, "minimum_currency": "USD", "currency_specific": { "USD": 10, "EUR": 10, "GBP": 10, "CAD": 15 } },
{ "id": "ramp", "name": "Ramp Network", "status": "active", "minimum_amount": 5, "minimum_currency": "USD", "currency_specific": { "USD": 5, "EUR": 5, "GBP": 5, "CAD": 8 } },
{ "id": "topper", "name": "Topper", "status": "active", "minimum_amount": 10, "minimum_currency": "USD", "currency_specific": { "USD": 10, "EUR": 10, "GBP": 10, "CAD": 10 } },
{ "id": "guardarian", "name": "Guardarian", "status": "active", "minimum_amount": 20, "minimum_currency": "USD", "currency_specific": { "USD": 20, "EUR": 20, "GBP": 20, "CAD": 20 } },
{ "id": "swapped", "name": "Swapped", "status": "active", "minimum_amount": 5, "minimum_currency": "USD", "currency_specific": { "USD": 5, "EUR": 5, "GBP": 5, "CAD": 5 } },
{ "id": "simplex", "name": "Simplex (Nuvei)", "status": "active", "minimum_amount": 50, "minimum_currency": "USD", "currency_specific": { "USD": 50, "EUR": 42, "GBP": 40, "CAD": 70 } },
{ "id": "finchpay", "name": "FinchPay", "status": "active", "minimum_amount": 35, "minimum_currency": "USD", "currency_specific": { "USD": 35, "EUR": 30, "GBP": 27, "CAD": 48 } }
]
}Provider Reference
stripeStripe (USA)
$3alchemyAlchemy Pay
$5cryptocomCrypto.com
$5paypalPayPal (USA)
$5rampRamp Network
$5robinhoodRobinhood (USA)
$5swappedSwapped
$5kryptonimKryptonim
$5simplexSimplex (Nuvei)
$50finchpayFinchPay
$35multihostedMultiHosted
$6revolutRevolut (EU/EEA)
$6transakTransak
$6banxaBanxa
$10coinbaseCoinbase
$10topperTopper
$10unlimitUnlimit (Global)
$12binanceBinance Connect
$15blockchainBlockchain.com
$15guardarianGuardarian
$20moonpayMoonPay
$20| Value | Name | Min Amount |
|---|---|---|
| stripe | Stripe (USA) | $3 |
| alchemy | Alchemy Pay | $5 |
| cryptocom | Crypto.com | $5 |
| paypal | PayPal (USA) | $5 |
| ramp | Ramp Network | $5 |
| robinhood | Robinhood (USA) | $5 |
| swapped | Swapped | $5 |
| kryptonim | Kryptonim | $5 |
| simplex | Simplex (Nuvei) | $50 |
| finchpay | FinchPay | $35 |
| multihosted | MultiHosted Aggregator | $6 |
| revolut | Revolut (EU/EEA) | $6 |
| transak | Transak | $6 |
| banxa | Banxa | $10 |
| coinbase | Coinbase | $10 |
| topper | Topper | $10 |
| unlimit | Unlimit (Global) | $12 |
| binance | Binance Connect | $15 |
| blockchain | Blockchain.com | $15 |
| guardarian | Guardarian | $20 |
| moonpay | MoonPay | $20 |
Error Handling
All errors follow a consistent Stripe-inspired format.
Error Response Format
{
"error": {
"type": "invalid_request_error",
"message": "Amount must be positive"
}
}HTTP Status Codes
validation_errorInvalid or missing parameters
invalid_request_errorBusiness rule violation (e.g. wallet not linked)
authentication_errorMissing or invalid API key
invalid_request_errorAction not allowed (e.g. sandbox-confirm with live key)
invalid_request_errorPayment not found or does not belong to you
Rate limited - too many requests
Internal server error
| Code | Type | Description |
|---|---|---|
| 400 | validation_error | Invalid or missing parameters |
| 400 | invalid_request_error | Business rule violation (e.g. wallet not linked) |
| 401 | authentication_error | Missing or invalid API key |
| 403 | invalid_request_error | Action not allowed (e.g. sandbox-confirm with live key) |
| 404 | invalid_request_error | Payment not found or does not belong to you |
| 429 | - | Rate limited - too many requests |
| 500 | - | Internal server error |