Quickstart
Create your first JoPay payment request in 5 minutes.
Overview
You will create a payment request, get a pay_page_url back, share it with a customer, and receive a webhook when they pay. Every step uses a single HTTP call — no SDK, no callback URLs, no OAuth dance.
jo_test_…) and a live key (jo_live_…). Start with a test key — test-mode requests are fully isolated from production data.Before you start
- Your partner has an account on JoPay and at least one merchant assigned to it. (If not, see Become a partner.)
- You have a partner API key. Create one in the partner portal (
/portal/api-keys) or ask your admin to create one. - You know the
merchant_idyou want to create the request for. Find it in the portal merchants list or viaGET /api/v1/merchants.
Your first payment request
Create the request
Post a minimal JSON body with the merchant id and the amount in minor units (2500 = €25.00 for a partner configured in EUR).
curl -X POST https://<partner-slug>.jopay.app/api/v1/requests/create \
-H "Authorization: Bearer jo_test_xxx" \
-H "Content-Type: application/json" \
-d '{
"merchant_id": "b4d5e6f7-...",
"fiat_amount_int": 2500,
"memo": "Invoice #INV-2026-0042",
"metadata": {
"order_id": "ORD-12345"
}
}'Read the response
You get back a request_id and a pay_page_url. The URL is what you share with the customer.
{
"ok": true,
"request_id": "a1b2c3d4-...",
"status": "requested",
"proof_status": "none",
"pay_page_url": "https://pay.jopay.app/a1b2c3d4-...",
"expires_at": "2026-04-17T18:00:00.000Z",
"created_at": "2026-04-17T17:00:00.000Z",
"amount": {
"fiat_int": 2500,
"fiat_code": "EUR",
"usdc_micro": "2750000"
},
"merchant_id": "b4d5e6f7-...",
"metadata": { "order_id": "ORD-12345" }
}Send the pay page URL to the customer
Email it, text it, embed it in a QR code — however you distribute checkout links today. The customer opens the URL and pays with any EVM wallet.
Receive the webhook
When the payment is verified on-chain, JoPay sends a payment.proof_verified event to your configured webhook URL. The payload includes your original metadata, so you correlate by metadata.order_id — no lookup table needed.
POST https://your-server.com/webhooks/jopay
x-jopay-event: payment.proof_verified
x-jopay-signature: v1=abc123...,t=1712345678
x-jopay-delivery: d1e2f3a4-...
{
"event": "payment.proof_verified",
"delivery_id": "d1e2f3a4-...",
"request_id": "a1b2c3d4-...",
"merchant_id": "b4d5e6f7-...",
"proof_status": "verified",
"tx_hash": "0x9f8e7d...",
"payer_address": "0x1a2b3c...",
"metadata": { "order_id": "ORD-12345" },
"timestamp": "2026-04-05T14:35:00.000Z"
}Same flow in JavaScript
const res = await fetch(
"https://<partner-slug>.jopay.app/api/v1/requests/create",
{
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.JOPAY_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
merchant_id: MERCHANT_ID,
fiat_amount_int: 2500,
memo: "Invoice #INV-2026-0042",
metadata: { order_id: "ORD-12345" },
}),
},
);
const { request_id, pay_page_url } = await res.json();
// Share pay_page_url with the customer via email / SMS / QR.
// JoPay will webhook your server when payment verifies.GET /api/v1/requests/:id until proof_status becomes verified. Webhooks are preferable — they remove the polling loop — but polling is a valid fallback during early integration.Test mode vs live mode
Keys come in two flavors. The prefix is authoritative:
jo_test_…— hits sandbox data, fully isolated from productionjo_live_…— hits real merchant and payment data
Test-mode requests are tagged test_mode: true, filtered out of live views, and never visible to live keys. Use test keys in CI and staging so a buggy push can't create real payment requests.
You cannot mix modes in one call: a test key against a live merchant (or vice versa) returns 400 merchant_mode_mismatch.