Skip to content
Draft: the API is under development and may change before the first release

Quickstart

  1. Get a test key.

    Keys look like sk_test_... and belong to a project. They are secret server-side keys: never put them in a browser or mobile app. A lost key is replaced, not recovered. The dashboard at dashboard.pachapay.io is a placeholder for now, so keys are not self-serve yet.

    Terminal window
    export PACHAPAY_KEY=sk_test_...
  2. Find a payment method.

    List the methods available for a country and currency. Use the code of a method in the next step.

    Terminal window
    curl "https://api.pachapay.io/v1/payment-methods?country=CL&currency=CLP" \
    -H "Authorization: Bearer $PACHAPAY_KEY"
  3. Create a payment.

    Every POST needs an Idempotency-Key. See Idempotency.

    Terminal window
    curl https://api.pachapay.io/v1/payments \
    -H "Authorization: Bearer $PACHAPAY_KEY" \
    -H "Idempotency-Key: order-8841-attempt-1" \
    -H "Content-Type: application/json" \
    -d '{
    "amount": 100000,
    "currency": "CLP",
    "country": "CL",
    "method_code": "CL.KHIPU",
    "merchant_reference": "order-8841",
    "payer": {
    "name": "Maria Lopez",
    "email": "maria@example.com",
    "document": { "type": "RUT", "number": "12345678-5" }
    },
    "return_url": "https://merchant.example/return"
    }'

    The amount is 100000 minor units. CLP has no decimals, so this is 100,000 pesos. For a currency with 2 decimals, 10.00 is sent as 1000.

    The response is the payment object. It is illustrative here, and fields not shown are omitted:

    {
    "id": "pay_0192f3a1",
    "object": "payment",
    "status": "requires_action",
    "amount": 100000,
    "amount_received": 0,
    "currency": "CLP",
    "country": "CL",
    "method_code": "CL.KHIPU",
    "method_family": "bank_redirect",
    "merchant_reference": "order-8841",
    "next_action": { "type": "redirect", "redirect_url": "https://bank.example/pay/abc" },
    "created_at": "2026-10-16T12:00:00Z"
    }

    With the default hosted flow the payment also has a checkout_url to send the payer to. Otherwise follow next_action. See Payments.

  4. Register a webhook endpoint.

    The URL must be HTTPS. The secret is shown once; store it.

    Terminal window
    curl https://api.pachapay.io/v1/webhook-endpoints \
    -H "Authorization: Bearer $PACHAPAY_KEY" \
    -H "Idempotency-Key: webhook-setup-1" \
    -H "Content-Type: application/json" \
    -d '{ "url": "https://merchant.example/webhooks/pachapay", "events": ["payment.succeeded", "payment.failed", "payment.expired"] }'
  5. Receive the event and verify the signature.

    Verify against the raw request body, before parsing JSON.

    import { createHmac, timingSafeEqual } from 'node:crypto';
    export function verify(rawBody: string, header: string, secret: string): boolean {
    const parts = Object.fromEntries(header.split(',').map((p) => p.split('=') as [string, string]));
    const t = parts.t;
    if (!t || Math.abs(Date.now() / 1000 - Number(t)) > 300) return false; // older than 5 minutes
    const expected = createHmac('sha256', secret).update(`${t}.${rawBody}`).digest();
    // During secret rotation the header holds two v1 values; accept either.
    return header
    .split(',')
    .filter((p) => p.startsWith('v1='))
    .some((p) => {
    const got = Buffer.from(p.slice(3), 'hex');
    return got.length === expected.length && timingSafeEqual(got, expected);
    });
    }

    Respond with any 2xx within 10 seconds. Handle duplicates by event.id. Details in Webhooks.

  6. Read the current state.

    Events can arrive out of order. Treat the webhook as a signal and read the object when it matters.

    Terminal window
    curl https://api.pachapay.io/v1/payments/pay_0192f3a1 \
    -H "Authorization: Bearer $PACHAPAY_KEY"