Create charges
How to create charges one by one or in bulk, with due dates, surcharge, states, payer notifications and safe retries.
This page covers how to create, track and cancel charges, one at a time or up to 100 per call. You need a connected merchant account and its id_token in Authorization: Bearer: see Connect merchant accounts.
How a payment request moves from one state to another.
draftbecomespendingwhen you set the amount with PATCH.pendingbecomestotal_paymentwith a full payment,partial_paymentwith a partial payment, andoverdueif it expires unpaid.partial_paymentbecomestotal_paymentwhen the rest is paid, andpartial_overdueif it expires first.draftandpendingbecomecanceledwhen you cancel them, if they received no payment.
1Create a charge
You identify each charge with your own referenceId, which comes back in every response and event.
From your backend, create the charge in Sandbox:
cURL
curl -X POST https://integration-api.test.chytapay.com.ar/payment-request \
-H "Authorization: Bearer $ID_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"referenceId": "invoice-1001",
"amount": 25000,
"description": "October installment",
"dueDates": ["2026-10-15", "2026-10-25"],
"surcharge": { "type": "percentage", "value": 10 },
"conciliationType": "cuil",
"sendWhatsappNotification": false,
"sendEmailNotification": true,
"customer": {
"name": "Jane Doe",
"email": "[email protected]",
"taxDocument": "20000000001"
},
"metadata": { "order": "1001" }
}'Node
const BASE = 'https://integration-api.test.chytapay.com.ar';
const res = await fetch(`${BASE}/payment-request`, {
method: 'POST',
headers: {
Authorization: `Bearer ${idToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
referenceId: 'invoice-1001',
amount: 25000,
description: 'October installment',
dueDates: ['2026-10-15', '2026-10-25'],
surcharge: { type: 'percentage', value: 10 },
conciliationType: 'cuil',
sendWhatsappNotification: false,
sendEmailNotification: true,
customer: {
name: 'Jane Doe',
email: '[email protected]',
taxDocument: '20000000001',
},
metadata: { order: '1001' },
}),
});
const charge = await res.json(); // 201Python
import requests
BASE = "https://integration-api.test.chytapay.com.ar"
res = requests.post(
f"{BASE}/payment-request",
headers={"Authorization": f"Bearer {id_token}"},
json={
"referenceId": "invoice-1001",
"amount": 25000,
"description": "October installment",
"dueDates": ["2026-10-15", "2026-10-25"],
"surcharge": {"type": "percentage", "value": 10},
"conciliationType": "cuil",
"sendWhatsappNotification": False,
"sendEmailNotification": True,
"customer": {
"name": "Jane Doe",
"email": "[email protected]",
"taxDocument": "20000000001",
},
"metadata": {"order": "1001"},
},
)
charge = res.json() # 201Response
{
"type": "standard",
"paymentRequestId": "<charge uuid>",
"referenceId": "invoice-1001",
"amount": 25000,
"description": "October installment",
"dueDates": ["2026-10-15", "2026-10-25"],
"state": "pending",
"surcharge": { "type": "percentage", "value": 10 },
"calculatedAmounts": [
{ "dueDate": "2026-10-15", "amount": 25000, "isOriginalAmount": true },
{ "dueDate": "2026-10-25", "amount": 27500, "surchargeApplied": 2500 }
],
"sendWhatsappNotification": false,
"sendEmailNotification": true,
"metadata": { "order": "1001" },
"customer": {
"name": "Jane Doe",
"email": "[email protected]",
"taxDocument": "20000000001"
},
"CVU": "<22-digit CVU>",
"bankAccountInfo": {
"accountHolderName": "<merchant account holder>",
"accountHolderTaxId": "<holder's CUIT>",
"bankName": "<bank>"
}
}Use dates within the next 35 days. The example reconciles by CUIL, the recommended mode: first, validate the document.
What it returns
201 with paymentRequestId, state (pending, or draft without an amount), CVU and bankAccountInfo (where the payer transfers) and, with an amount and 2 dates, calculatedAmounts (each due date with surcharge). It also returns what you sent, except customer.taxDocument in CVU mode.
2Retrieve a charge
Request it with GET /payment-request/{referenceId}:
curl https://integration-api.test.chytapay.com.ar/payment-request/invoice-1001 \
-H "Authorization: Bearer $ID_TOKEN"Same shape as the create response, with an updated state. If it doesn't exist for the merchant account, 404. To list the charges created in a range, use GET /payment-request?startDate=YYYY-MM-DD&endDate=YYYY-MM-DD, both dates optional.
3Duplicates and retries
There's no idempotency key: the referenceId is unique per integration and merchant account, canceled charges included. Repeating it returns 409 without the existing charge:
{
"error": "ReferenceIdAlreadyExists",
"message": "The provided reference ID already exists for this client",
"referenceId": "invoice-1001"
}Use your entity's id (invoice, order, installment), not one per attempt. If a create times out:
- Call
GET /payment-request/{referenceId}. If it returns 200, the charge was created: use that response. - If it returns 404, retry the
POSTwith the same body andreferenceId. - If the
POSTreturns 409, another attempt created it: go back to step 1.
If the charge from the GET doesn't match what you meant to create, you reused an old referenceId by mistake.
4Cancel a charge
Call POST /payment-request/{referenceId}/cancel with no body:
cURL
curl -X POST https://integration-api.test.chytapay.com.ar/payment-request/invoice-1001/cancel \
-H "Authorization: Bearer $ID_TOKEN"Response
{ "referenceId": "invoice-1001", "state": "canceled" }Only charges in draft or pending with no payments can be canceled: otherwise, 409 PaymentRequestCannotBeCanceled. It generates no event: the 200 response is the confirmation. If a transfer comes in later, you get payment.unmatched with reason: "canceled", which is opt-in.
Fields
The fields of a standard charge. Omit the optional fields you don't use; don't send null.
| Field | Required | Rules |
|---|---|---|
referenceId |
Yes | 8 to 100 characters: letters, numbers, - and _. See duplicates. |
description |
Yes | What the payer sees. 3 to 500 characters. |
due |
Yes | 1 or 2 dates: see due dates. |
customer |
Yes | The payer (table below). |
amount |
No | Pesos, greater than 0, up to 2 decimals, cap of 200,000,000. Without it, a draft. |
surcharge |
With amount and 2 dates | { "type", "value" } for the second due date. Not allowed without amount. |
send |
With amount | WhatsApp notifications. Without an amount, only false. |
send |
With amount | Email notifications. Without an amount, only false. |
send |
No | See payment confirmation. |
reminder |
No | YYYY-. Replaces the advance reminder. Only with an amount. |
deferred |
No | YYYY-. Defers the initial notification. Only with an amount. |
conciliation |
No | cvu (default) or cuil, the recommended one. |
webhook |
No | HTTPS, up to 2048 characters. Overrides the webhook URL for this charge. |
metadata |
No | Strings: up to 50 keys of 40 characters, values of 500, 8 KB. Returned in the GET and in events. |
customer field |
Required | Rules |
|---|---|---|
name |
Yes | 2 to 150 characters. |
tax |
With conciliation |
11-digit CUIL or 7- or 8-digit DNI. Ignored in CVU mode. |
email |
With send |
Valid, up to 320 characters. |
phone |
With send |
{ "country, no spaces, 6 to 15 digits. |
Outside these rules, 400 with the details: see the errors.
Due dates and surcharge
dueDates takes 1 or 2 dates, after today and no more than 35 days out:
| Format | Example | Due |
|---|---|---|
YYYY- |
"2026- |
At the end of that day, Argentina time. |
YYYY- |
"2026- |
At that minute. Without a zone it's -; accepts Z or an offset. |
With a time, the charge is short-lived. With 2 dates, the second element of dueDates carries surcharge, required with an amount (Chytapay doesn't reorder them). The payer owes amount up to the first due date and amount plus the surcharge from the day after.
type is percentage (0 to 100, on the amount), fixed (pesos, up to 200,000,000) or none (value: 0). With amount: 25000 and { "type": "percentage", "value": 10 }, the second due date is 27,500, in calculatedAmounts.
Short-lived charges
For reservations, tickets or any charge that has to close at a set time, send the due date with a time: "2026-10-15T18:30". It changes several things compared to a date alone:
| Date alone | With a time | |
|---|---|---|
| Expires | At the end of that day, Argentina time | At that minute. Without a zone it's - |
| Due dates | 1 or 2, with a surcharge on the second | A single one, no surcharge |
| Event on expiry | None | payment_ |
| Payer notifications | Reminder one day before | With less than 24 hours there's no advance reminder: the initial and due-date ones remain |
| In bulk | Yes | No, it returns 400 |
If the charge is paid in the widget, the checkout charge expires between 3 and 60 minutes.
Draft without an amount
Without amount, the charge starts in draft, with one due date, no surcharge and no notifications (flags only false, notification dates not allowed). Complete it with PATCH /payment-request/{referenceId}:
cURL
curl -X PATCH https://integration-api.test.chytapay.com.ar/payment-request/invoice-1002 \
-H "Authorization: Bearer $ID_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "amount": 18000 }'Response
{
"referenceId": "invoice-1002",
"previousAmount": null,
"amount": 18000,
"description": "Enrollment",
"state": "pending",
"dueDates": ["2026-10-20"]
}- Only in
draftwith no payments; otherwise, 409. - It moves to
pending. A draft never sends notifications, not even after the PATCH. - If you don't complete it, Chytapay discards it after a few hours.
States
Create, GET, cancel, PATCH and bulk return state, and payment_request.* events return stateType. There's no PAID: it's total_payment.
| State | Meaning |
|---|---|
draft |
Draft without an amount. |
pending |
Waiting for payment. |
partial_ |
Partially paid, balance left. |
total_ |
Paid. |
overdue |
Expired unpaid. |
partial_ |
Expired partially paid. |
canceled |
Canceled. |
With 2 dates, the charge stays open until the second due date. A day-only date expires at 00:00 Argentina time, with no event; one with a time expires at that minute with payment_request.expired. See the events in Webhooks.
Payer notifications
A payer notification is a WhatsApp or email from Chytapay to the payer (not an event to your backend). Channels are chosen in the fields, only with an amount. After a partial payment, reminders show the balance; paid, past due or canceled, they stop.
When each notification goes out
Reminders go out at 10 a.m. Argentina time.
See the notification schedule
| Notification | When | Amount |
|---|---|---|
| Initial | On creation, or on deferred |
First due date, no surcharge |
| Advance reminder | The day before the first due date, or on reminder |
First due date, no surcharge |
| Due date | That day | First due date, no surcharge |
| First due date passed (2 dates) | The day after | Second due date, with surcharge |
| Second due date (2 dates) | That day | Second due date, with surcharge |
Both dates must be after today and before the first due date, and reminderNotificationDate also after deferredInitialNotificationDate.
Payment confirmation
The WhatsApp payment received notification is controlled by sendWhatsappPaymentConfirmation, not sendWhatsappNotification:
| Value | CVU mode | CUIL mode |
|---|---|---|
| Omitted | Sent, on full and partial payments | Not sent |
true |
Sent, on full and partial payments | Only on full payment |
false |
Not sent | Not sent |
In CUIL mode the payer can request it with "Recibir confirmación de pago" (Receive payment confirmation), and false doesn't turn that off.
Create charges in bulk
POST /payment-request/bulk creates 1 to 100 charges, all or nothing. Each item is a single charge, except that:
- It always reconciles by CUIL:
customer.taxDocumentis required. - The same document twice in a batch returns 422.
- A date with a time returns 400.
conciliationType,type,expiresInMinutesandwebhookUrlaren't allowed: events go to the integration's URL.
From your backend, send the batch:
cURL
curl -X POST https://integration-api.test.chytapay.com.ar/payment-request/bulk \
-H "Authorization: Bearer $ID_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"paymentRequests": [
{
"referenceId": "installment-2026-10-001",
"amount": 12000,
"description": "October installment",
"dueDates": ["2026-10-15"],
"sendWhatsappNotification": false,
"sendEmailNotification": false,
"customer": { "name": "Jane Doe", "taxDocument": "20000000001" }
},
{
"referenceId": "installment-2026-10-002",
"amount": 12000,
"description": "October installment",
"dueDates": ["2026-10-15"],
"sendWhatsappNotification": false,
"sendEmailNotification": false,
"customer": { "name": "John Smith", "taxDocument": "30405060" }
}
]
}'Response
{
"batchId": "<batch uuid>",
"total": 2,
"created": 2,
"sharedCvu": "<22-digit CVU>",
"accountInfo": {
"accountHolderName": "<merchant account holder>",
"accountHolderTaxId": "<holder's CUIT>",
"bankName": "<bank>"
},
"paymentRequests": [
{ "referenceId": "installment-2026-10-001", "state": "pending" },
{ "referenceId": "installment-2026-10-002", "state": "pending" }
]
}paymentRequests follows the request order; retrieve details with GET.
See the bulk responses
| Code | When |
|---|---|
| 201 | All created: store batchId. |
| 400 | Empty array, more than 100 items or a field outside the contract. |
| 422 | errors: [{ index, referenceId, reason }] with every failed item: fix them and resend the batch. |
| 429 | The batch doesn't fit entirely under the WhatsApp message cap: nothing is created. See the limits. |
With 400, 422 or 429 nothing is created. Some reason values: Duplicate referenceId within the batch, The referenceId already exists for this integration, The taxDocument does not resolve to a valid CUIL and Duplicate payer within the batch: ....
sendWhatsappPaymentConfirmation applies to the whole batch and, if items differ, false wins. Notification channels work the other way: if any item turns on WhatsApp or email, it's on for the batch.
Checkout charge
A type: "checkout" charge is paid from the widget: see the checkout charge.