Void a charge
LiveServed by the v1 API in development with the same checks as production; production opens on the same contract. The capabilities from before v1 keep running in production at the path your account team gives you.
Use it when the card payment is reversed before the customer invoices: a wrong amount, a refund or a bank reversal. The charge becomes void, its link stops working and it never goes into the global invoice. If the customer already invoiced it, cancel the invoice instead; this answers 409. A charge that expired or was already voided answers 410.
Needs the charges:void scope.
oauth2 charges:voidAuthorizationBearer <token>OAuth 2.0 client credentials. Exchange your client_id and client_secret at /oauth/token of the environment for a bearer token valid for one hour. The playground of every operation asks for your own client and keeps it in your browser tab only.
Scope: charges:void
id*stringThe charge id.
^chg_[0-7][0-9a-hjkmnp-tv-z]{25}$Idempotency-Key*stringUnique per operation. Retrying with the same key returns the original result for 24 hours.
length <= 255application/json- body
reason?stringWhy the payment was undone. It shows in your reports.
"wrong_amount""refund""reversal""duplicate"The charge is void.
application/json- response
id*stringThe charge in this API: use it to get or void it.
^chg_[0-7][0-9a-hjkmnp-tv-z]{25}$external_id*stringYour transaction id, as you sent it. Webhooks carry it so you can match them to your records.
status*stringopen while the customer can invoice it; invoiced once they did; expired when expires_at passed unused; void after POST /charges/{id}/void; included_in_global when an expired charge went into the merchant's global invoice.
"open""invoiced""expired""void""included_in_global"merchant_id*stringThe merchant that made the sale.
^mer_[0-7][0-9a-hjkmnp-tv-z]{25}$amount*stringTotal charged to the card, VAT included.
^[0-9]+\.[0-9]{2}$payment_form*stringSAT c_FormaPago: 04 credit card, 28 debit card.
"04""28"occurred_at*stringWhen the card payment was approved.
date-timelocation?|external_id of the merchant's location, if you sent it.
items*array<>What the invoice will say. If you sent no items, it holds the merchant's default_item for the whole amount.
invoice_token*|Code inside invoice_url. Use it only if you build the link yourself, for example on your own domain. It is not the charge id. Null once the charge is no longer open.
invoice_url*|Link the customer opens to invoice this charge: https://invoice.heru.app/{merchant slug}/c/{invoice_token}. It works once and until expires_at. Null once the charge is no longer open.
qr*|QR with invoice_url. Null once the charge is no longer open.
expires_at*stringLast moment the customer can invoice the charge.
date-timeinvoice*|The CFDI the customer issued. Null until status is invoiced. Download its files with GET /invoices/{id}/download.
created_at*stringdate-timeupdated_at*stringdate-timecurl -X POST 'https://api.dev.b2b.heru.app/v1/charges/chg_01m4p7sbvpev1s5086cty36rvs/void' \ -H 'Idempotency-Key: try-it-0001' \ -H 'Content-Type: application/json' \ -d '{ "reason": "wrong_amount"}'{ "id": "chg_01m4p7ne2nejtbv6n6b53xttxm", "external_id": "TXN-0001845121", "status": "void", "merchant_id": "mer_01m4ra8wsyfh3s0tr4nrq3y96j", "amount": "520.00", "payment_form": "28", "occurred_at": "2026-10-11T13:40:01-06:00", "location": "SUC-01", "items": [ { "product_code": "90101500", "description": "Consumo de alimentos y bebidas", "amount": "520.00", "quantity": 1, "unit_code": "E48", "vat_rate": "0.16" } ], "invoice_token": null, "invoice_url": null, "qr": null, "expires_at": "2026-11-10T23:59:59-06:00", "invoice": null, "created_at": "2026-10-11T19:40:02.517Z", "updated_at": "2026-10-11T19:41:15.157Z"}Create a chargePOST
Call it when a card payment is approved, with what was sold and its VAT. If you only know the amount, leave `items` out and the invoice uses the merchant's `default_item`. Returns a single-use link and its QR that you show on the terminal or print on the receipt. The customer opens the link, enters their tax data, we validate it with the SAT and stamp the invoice immediately, with no action from the merchant. Needs the `charges:write` scope.
Upload the CSDPUT
Uploads or replaces the merchant's CSD from your back end, for integrations that already hold it. Prefer the onboarding page when you can: with it you never handle the merchant's certificate or password. heru checks the files and calculates the merchant status from the certificate dates. When a check fails it answers `422` and keeps the current CSD; every code is explained in [CSD errors](/errors/csd). Needs the `merchants:csd` scope.