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

  1. Open the console with your GatoPago account and create an API key.
  2. Create a charge from your server and send your customer its checkout_url.
  3. Register a webhook URL and deliver the order when payment_intent.succeeded arrives.

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).

Request
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
  }'
Response · 201
{
  "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.

Event
{
  "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.

Node.js
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_intentsCreates a charge.
GET /v1/payment_intentsLists the latest 100.
GET /v1/payment_intents/:idReads one.
POST /v1/payment_intents/:id/cancelCancels an unpaid one.
POST /v1/payment_intents/:id/simulateMarks it as paid (test networks only).
GET /v1/eventsEvents and how each delivery went.
POST /v1/events/:id/resendSends an event again.
GET · POST /v1/webhook_endpointsLists or registers webhook URLs.
DELETE /v1/webhook_endpoints/:idRemoves a URL.
GET · POST /v1/api_keysAPI 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).