GatoPago Flow · API
Get paid in USDC from your server
Create a charge, share its link or QR, and receive a signed webhook when it is paid. Your customer pays from GatoPago or from any wallet, and the money reaches your account directly: GatoPago never holds it.
Public beta on test networks: use test keys (sk_test_…) and test USDC.
Quick start
- Open the console with your GatoPago account and create an API key.
- Create a charge from your server and send your customer its
checkout_url. - Register a webhook URL and deliver the order when
payment_intent.succeededarrives.
Authentication
Every request carries your key: Authorization: Bearer sk_test_…. It creates charges in your name, so keep it on your server, never in an app or a web page. If it leaks, revoke it in the console and create another.
Create a charge
Only amount is required: a decimal in USDC with up to 6 decimals. description (up to 200 characters) is shown to the payer; metadata (up to 20 text values) is for you; expires_in is in seconds, from 5 minutes to 7 days (24 hours by default).
curl -X POST https://api.gatopago.com/v1/payment_intents \
-H "Authorization: Bearer sk_test_…" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-1042" \
-d '{
"amount": "18.00",
"description": "Order 1042",
"metadata": { "order_id": "1042" },
"expires_in": 3600
}'{
"id": "pi_3f9c…",
"object": "payment_intent",
"amount": "18.00",
"currency": "USDC",
"status": "requires_payment",
"description": "Order 1042",
"metadata": { "order_id": "1042" },
"checkout_url": "https://gatopago.com/pay/pi_3f9c…",
"expires_at": 1767229200,
"created_at": 1767225600,
"livemode": false,
"payment": null
}Retry safely: with the same Idempotency-Key and the same data you get the same charge back, never a second one. If the response was lost, repeat the request before creating another.
Statuses
requires_payment- Waiting for payment.
processing- Paid from another network; Circle is delivering the USDC to yours.
succeeded- Paid: the money is in your account.
canceled- You canceled it before it was paid.
expired- It expired unpaid (after 24 hours by default).
On test networks, POST /v1/payment_intents/:id/simulate marks a charge as paid, so you can try your webhook without paying.
Webhooks
Register your URL in the console. GatoPago sends each event with a POST and retries failed deliveries up to 10 times; answer with a 2xx as soon as you receive it.
{
"id": "evt_…",
"object": "event",
"type": "payment_intent.succeeded",
"created_at": 1767226000,
"data": { "id": "pi_3f9c…", "status": "succeeded", "amount": "18.00", "…": "…" }
}payment_intent.created- A charge was created.
payment_intent.processing- It was paid from another network and is on its way.
payment_intent.succeeded- It was paid: deliver the order.
payment_intent.duplicate_payment- A second payment arrived for a charge already paid.
payment_intent.canceled- It was canceled.
payment_intent.expired- It expired unpaid.
Verify every event. The GatoPago-Signature header is t=<unix>,v1=<hex>: an HMAC-SHA256 of <t>.<body> with your endpoint’s secret (whsec_…), which the console shows once.
import { createHmac, timingSafeEqual } from 'node:crypto';
// Use the raw body, exactly as it arrived.
export function verifyGatoPago(rawBody, header, secret) {
const { t, v1 } = Object.fromEntries(header.split(',').map((part) => part.split('=')));
// Reject old deliveries (5 minutes).
if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false;
const expected = createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
return v1.length === expected.length && timingSafeEqual(Buffer.from(v1), Buffer.from(expected));
}Reference
Base URL: https://api.gatopago.com
POST /v1/payment_intents | Creates a charge. |
GET /v1/payment_intents | Lists the latest 100. |
GET /v1/payment_intents/:id | Reads one. |
POST /v1/payment_intents/:id/cancel | Cancels an unpaid one. |
POST /v1/payment_intents/:id/simulate | Marks it as paid (test networks only). |
GET /v1/events | Events and how each delivery went. |
POST /v1/events/:id/resend | Sends an event again. |
GET · POST /v1/webhook_endpoints | Lists or registers webhook URLs. |
DELETE /v1/webhook_endpoints/:id | Removes a URL. |
GET · POST /v1/api_keys | API keys: from the console only. |
Errors answer with an HTTP status and a stable code in error_code:
UNAUTHENTICATED- The key is missing or invalid (401).
INVALID_AMOUNT- The amount is not a valid decimal above zero (400).
IDEMPOTENCY_KEY_REUSED- You used the same Idempotency-Key with other data (409).
INTENT_NOT_CANCELABLE- The charge was already paid, expired or canceled (409).
RATE_LIMITED- Too many requests; retry later (429).