Crypt2Merchant API
Accept card payments from any website, app, or platform. Two API calls, one callback, and that's it.
https://api.crypt2merchant.net · Hosted checkout: https://dashboard.crypt2merchant.net/checkoutonramp_unavailable_region). Offer them another payment method.Authentication
All API requests require your merchant API key in the X-Api-Key header. Your key starts with c2m_ and is provided when your account is created.
X-Api-Key: c2m_your_api_key_here Content-Type: application/json
Create Payment Session
Start a payment by creating a session. This generates a unique deposit address and returns the details needed to redirect the customer.
Request Body
| Parameter | Type | Description | |
|---|---|---|---|
| amount_fiat | number | required | Payment amount in USD. Minimum $20. Stripe delivers this exact amount in USDC. Its fee is charged to the customer on top. |
| currency | string | optional | Must be USD (the default). Other codes return 400 unsupported_currency. Convert first with /api/currency/convert. |
| order_id | string | optional | Your internal order reference (max 128 chars). Returned in the callback. Repeating a pending order's ID returns the same session. |
| callback_url | string | optional | Absolute https URL we GET when payment is confirmed. Must return HTTP 200. |
curl -X POST https://api.crypt2merchant.net/api/session \
-H "X-Api-Key: c2m_your_key" \
-H "Content-Type: application/json" \
-d '{
"amount_fiat": 50.00,
"currency": "USD",
"order_id": "order-123",
"callback_url": "https://yoursite.com/payment-callback"
}'
{
"session_id": "7a004150-8509-47de-b6fe-5b62267210d7",
"deposit_address": "0xC690783E29bF60DE7AEfE74Dd9b455cA8980Ce14",
"splitter_address": "0xA03f79A3B0834558a21a8dD7F3F6aeFD9931Cc53",
"amount_fiat": "50.00",
"currency": "USD"
}
Response Fields
| Field | Description |
|---|---|
| session_id | Unique payment session identifier. Use this in the checkout redirect. |
| deposit_address | The unique on-chain address this payment settles to. Informational. The hosted checkout locks it in automatically. |
| splitter_address | Your merchant's smart contract address. |
| amount_fiat | The confirmed payment amount. |
Redirect to Payment Page
After creating a session, redirect the customer to our hosted checkout page on dashboard.crypt2merchant.net. They'll complete payment via card, so you don't need to build any payment UI. Returning customers who have paid this way before skip identity verification.
Query Parameters
| Parameter | Type | Description | |
|---|---|---|---|
| session_id | string | required | From the create session response. |
| amount | number | optional | Shown while the page loads. It is then replaced by the server-side amount, which is what gets charged. |
| currency | string | optional | Display only. Default: USD |
https://dashboard.crypt2merchant.net/checkout ?session_id=7a004150-8509-47de-b6fe-5b62267210d7 &amount=50.00
Payment Callback
When payment is confirmed, we send a GET request to the callback_url you provided when creating the session. Your server must return HTTP 200 to acknowledge receipt.
Query Parameters (sent by us)
| Parameter | Description |
|---|---|
| session_id | The payment session that was completed. |
| txid_out | The blockchain transaction hash. Verifiable on Basescan. |
| value_coin | USDC received at the deposit address. Normally equals amount_fiat; the customer may edit the amount inside the payment form, so accept anything within your tolerance and treat a shortfall as underpaid. |
| order_id | Your order reference, as provided when creating the session. |
session_id may be delivered more than once.Convert Currency
Sessions are always charged in USD. If your store prices in another currency, convert the total first and pass the USD figure as amount_fiat. Rates come from the European Central Bank feed and are cached for an hour.
{ "amount": 45, "from": "EUR", "to": "USD", "converted": 52.31 }
Full Integration Examples
Complete working examples in popular languages. Each shows the full flow: create session, redirect, handle callback.
const express = require('express');
const app = express();
const API_KEY = 'c2m_your_key';
const API_URL = 'https://api.crypt2merchant.net';
const CHECKOUT_URL = 'https://dashboard.crypt2merchant.net/checkout';
// 1. Customer clicks "Pay" — create session and redirect
app.post('/checkout', async (req, res) => {
const response = await fetch(`${API_URL}/api/session`, {
method: 'POST',
headers: { 'X-Api-Key': API_KEY, 'Content-Type': 'application/json' },
body: JSON.stringify({
amount_fiat: req.body.total,
currency: 'USD',
order_id: req.body.orderId,
callback_url: 'https://yoursite.com/payment-done',
}),
});
const session = await response.json();
// 2. Redirect customer to hosted payment page
res.redirect(
`${CHECKOUT_URL}?session_id=${session.session_id}`
+ `&amount=${session.amount_fiat}`
);
});
// 3. We call this URL when payment is confirmed
app.get('/payment-done', (req, res) => {
const { session_id, txid_out, value_coin, order_id } = req.query;
// Mark order as paid in your database
console.log(`Order ${order_id} paid! Amount: ${value_coin}`);
res.sendStatus(200); // Must return 200
});Error Handling
Every error response has an error field. Machine-readable failures use a stable snake_case code there plus a human-readable message; simple validation failures put the message directly in error.
| Status | Code | Meaning |
|---|---|---|
| 400 | Missing or invalid parameters (message in error). | |
| 400 | unsupported_currency | Only USD is accepted. Convert first. |
| 400 | amount_below_minimum | Below the $20 card minimum (min_amount is included). |
| 400 | onramp_unavailable_region | Card payments are not available where the customer is (New York, EU). Shown on the payment page. |
| 401 | Invalid or missing API key. | |
| 404 | not_found | Unknown session. |
| 410 | session_closed | The session has expired or was already paid. Create a new one. |
| 429 | Rate limited (100 requests/minute per IP). Back off and retry. | |
| 500 | Server error. Try again or contact support. | |
| 502 | onramp_unavailable | Payment provider error. Try again. |
| 503 | deposit_pool_empty | Deposit addresses are being provisioned for your account. Retry after the Retry-After header (60s). |
{ "error": "amount_below_minimum", "message": "Minimum card payment is $20.00 USD.", "min_amount": 20 }