Skip to content
ESENGo to the Portal

Getting started

From sign-up in the portal to your first event in your backend, all in Sandbox: credentials, a connected merchant account, a charge, a simulated payment and the webhook.

By the end you will have a Sandbox charge paid with a simulated payment and the payment_request.payment_received event in your backend. No real money moves.

Before you begin

  • A public HTTPS URL for webhooks. During development, a tunnel works.
  • curl, Node 18 or later, or Python 3 with requests.

Set the Sandbox API URL in your terminal. The Production one is in Operations and errors.

cURL
export API=https://integration-api.test.chytapay.com.ar

1Sign up and set up

You sign up yourself in the portal, with no one to wait for. You start in Sandbox, the test environment.

  1. Go to chytapay.com.ar/portal/login, choose Crear cuenta (Create account), confirm your email and fill in your company details. When you finish, the portal shows your Sandbox clientId and clientSecret.
  2. Store them in your secrets manager. The clientSecret is shown only once: if you lose it, rotate it under Credenciales (Credentials). You don't use them in this walkthrough: they are for OAuth with real merchant accounts.
  3. Add your HTTPS webhook URL under Configuración (Settings). The validation token is optional, at least 16 characters long, and arrives in the validation-token header.

2Get a test merchant account

The Integration API takes as Bearer the idToken of a merchant account linked to your integration. It lasts 1 hour.

Sandbox shortcut: a test merchant account

In Sandbox you don't need OAuth: the portal gives you the token of a test merchant account that is already linked. Go to Cuentas de comercio (Merchant accounts), choose Generar tokens de prueba (Generate test tokens) and store the idToken as COMMERCE_TOKEN. It lasts 1 hour: when it expires, generate another. To check which merchant the token belongs to, call GET /me.

The real flow: OAuth

In Production, each merchant account authorizes your integration with OAuth: authorize, return to your redirect_uri with code and state, and exchange for tokens. The redirect_uri is not the webhook URL. See how to connect merchant accounts with OAuth.

3Create a charge

In your backend, create the charge with the merchant account token and a due date between tomorrow and 35 days out. Send "conciliationType": "cuil" and the payer's CUIL or DNI in customer.taxDocument: the payment is matched by the CUIL of whoever transfers (why CUIL).

cURL

DUE_DATE=$(date -d '+7 days' +%F 2>/dev/null || date -v+7d +%F)

curl -X POST "$API/payment-request" \
  -H "Authorization: Bearer $COMMERCE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "referenceId": "test-0001",
    "amount": 1500,
    "description": "Test charge",
    "dueDates": ["'"$DUE_DATE"'"],
    "conciliationType": "cuil",
    "sendWhatsappNotification": false,
    "sendEmailNotification": false,
    "customer": { "name": "Test payer", "taxDocument": "20123456786" }
  }'

Node

const dueDate = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString().slice(0, 10);

const res = await fetch(`${process.env.API}/payment-request`, {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.COMMERCE_TOKEN}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    referenceId: 'test-0001',
    amount: 1500,
    description: 'Test charge',
    dueDates: [dueDate],
    conciliationType: 'cuil',
    sendWhatsappNotification: false,
    sendEmailNotification: false,
    customer: { name: 'Test payer', taxDocument: '20123456786' },
  }),
});
const charge = await res.json();
console.log(res.status, charge.state, charge.CVU);

Python

from datetime import date, timedelta

due_date = (date.today() + timedelta(days=7)).isoformat()

res = requests.post(
    f"{os.environ['API']}/payment-request",
    headers={"Authorization": f"Bearer {os.environ['COMMERCE_TOKEN']}"},
    json={
        "referenceId": "test-0001",
        "amount": 1500,
        "description": "Test charge",
        "dueDates": [due_date],
        "conciliationType": "cuil",
        "sendWhatsappNotification": False,
        "sendEmailNotification": False,
        "customer": {"name": "Test payer", "taxDocument": "20123456786"},
    },
)
charge = res.json()
print(res.status_code, charge["state"], charge["CVU"])

Response

{
  "type": "standard",
  "paymentRequestId": "00000000-0000-0000-0000-000000000000",
  "referenceId": "test-0001",
  "amount": 1500,
  "description": "Test charge",
  "dueDates": ["2026-10-07"],
  "state": "pending",
  "sendWhatsappNotification": false,
  "sendEmailNotification": false,
  "customer": { "name": "Test payer", "taxDocument": "20123456786" },
  "CVU": "0000000000000000000000",
  "bankAccountInfo": { "accountHolderName": "...", "accountHolderTaxId": "...", "bankName": "..." }
}

You get a 201 with the charge in pending. Store the CVU in the CVU variable for stage 4.

  • referenceId is 8 to 100 characters (letters, numbers, - and _) and unique per integration and merchant account, canceled charges included. A repeat returns 409 ReferenceIdAlreadyExists: to test again, change it.
  • With amount, sendWhatsappNotification and sendEmailNotification are required. In Sandbox, false.
  • Amounts in pesos, with up to 2 decimal places.

The other fields, surcharges, drafts and batches are in Create charges.

4Simulate the payment

Simulate full payment in the Portal, under Simulador de pagos (Payment simulator): enter the charge's CVU, the amount and the payer's details: source CVU, source name and, as the source CUIL, the charge's.

  • With the charge's CUIL, the payment is matched and payment_request.payment_received arrives.
  • With a different CUIL it does not reconcile and the event is payment.unmatched.
  • If the event doesn't arrive, check that it is enabled under Webhooks and that you have a URL set under Configuración.

Warning: payment.unmatched is off by default. Enable it in Webhooks: without it you never learn about unmatched payments, such as a third party paying with a different CUIL.

5Receive the event

Chytapay sends a POST to your webhook URL. Return a 2xx within 5 seconds or it retries.

Header Purpose
X-Chytapay-Signature HMAC-SHA256 signature, t=<unix>,v1=<hex>. Verify it before you trust the event
X-Chytapay-Webhook-Id Same across all retries: deduplicate by this ID
validation-token The token you set with the URL

The body, trimmed:

JSON
{
  "event": "payment_request.payment_received",
  "stateType": "total_payment",
  "livemode": false,
  "referenceId": "test-0001",
  "requestedAmount": 1500,
  "paidAmount": 1500,
  "thisPaymentAmount": 1500,
  "remainingAmount": 0,
  "reconciledPayerCuil": 20123456786
}

Route on event and decide on stateType: total_payment or partial_payment. livemode is false in Sandbox.

The example server, full signature verification and retries are in Webhooks. To test only your endpoint, Webhooks has a button that sends a test payment.unmatched. It does not exist in production.

Go to production

It is the same integration: you request Production under Credenciales (Credentials), and if the automatic check approves it you generate the production credentials with the environment selector on Production. If not, the request goes to review. What to check before you collect is in Operations and errors.

Next steps