Void, refund and receipts
13.0 There is no authorize-then-capture, and where that limit comes from
Connect charges in one step. A payment authorizes and captures together - a sale - and there is no endpoint that places a hold now and takes the money later.
If you ship goods, this is the constraint to design around: you cannot reserve funds at checkout and capture when the box leaves the warehouse. What you have instead is reversal after the fact - void while the transaction is unsettled, refund once it is not, both in this section.
Whose limit it is, answered by the API team on 2026-08-25, because it changes how you plan:
| Does the processor support it? | Yes. PowerTranz has authorize and capture as separate calls |
| Does the acquiring bank block it? | No |
| So why is it missing? | Connect does not expose it. It is an API/product gap, not a technical one |
That is worth knowing rather than assuming. A limit that comes from the processor is permanent and you build around it forever; a limit that is "not exposed yet" is a roadmap item worth asking for when you have a merchant who needs it.
13.0b 3-D Secure: version, and who carries the fraud loss
Connect authenticates with 3DS2 (EMV 3-D Secure 2.x); the processor reports the exact
sub-version, e.g. 2.1.0.
When authentication succeeds, fraud liability can move from the merchant to the card issuer. There is no API call for it and no switch to turn on: it follows automatically from the card network's own rules, when the ECI value, the authentication status and the proof of authentication (the CAVV) line up.
| Network | Fully authenticated | Attempted | Failed - no shift |
|---|---|---|---|
| Visa / Amex | ECI 05, status Y, CAVV present | ECI 06, status A, CAVV present | ECI 07, status N / U / R |
| Mastercard | ECI 02, status Y, CAVV present | ECI 01, status A, CAVV present | ECI 00, status N / U / R |
Two caveats, both stated by the API team:
- The shift is only available for certain merchant category codes. Which ones qualify is not published; your acquiring bank knows your account's code and whether it does.
- A merchant enrolled in Visa's fraud monitoring programme can still carry the loss even after a successful authentication.
None of this is visible on the Connect payment object. The processor returns the ECI, the CAVV,
the authentication status and the protocol version, but Connect does not surface any of them, and
there is no liability_shift flag. So you cannot branch on it, and you cannot show a merchant
whether a given payment shifted. Treat authentication as something that happened, not as something
you can read back.
These act on a transaction, identified by the transaction_id UUID from the payment
response - never by the numeric payment id.
That distinction matters because a reusable link can carry several transactions: voiding or refunding one customer's transaction leaves the others untouched.
| Operation | Path |
|---|---|
| Void | POST /payments/{transaction_id}/void |
| Refund | POST /payments/{transaction_id}/refund |
| Receipt metadata | GET /payments/{transaction_id}/receipt |
| Receipt PDF | GET /payments/{transaction_id}/receipt/download |
13.1 Passing the wrong identifier
These routes want the transaction UUID. Hand them the numeric payment id and the endpoint answers
404 with code: "transaction_id_expected", naming the identifier it wanted:
POST /payments/9e2b6a34-6f1d-4e2a-8c9b-2f7a1d4e5c6b/void -> the route matched
POST /payments/873/void -> 404 {"code":"transaction_id_expected",
"message":"This endpoint requires
transaction_id (the UUID from the
payment or charge response), not
the payment numeric id."}
That is a live endpoint refusing the identifier, not a missing route. The distinction matters
because the two have opposite fixes: not_found means change the URL, transaction_id_expected
means change the identifier.
Changed 2026-08-21. This case used to return the routing 404 (
"The route ... could not be found."), and earlier versions of this guide - and the sample clients in it - told you to detect it that way. If your integration still branches on"could not be found", it now misreads this error as "payment does not exist". Branch oncode. The message here is localized; the routing 404 prose is not.
13.2 Void or refund?
| Void | Refund | |
|---|---|---|
| When | Same day, before settlement | After settlement |
| Amount | Full only | Full or partial |
| Speed | Immediate | 3-5 business days back to the card |
| Customer sees | Never actually charged | Money returned |
| Webhook | payment.voided |
payment.refunded / payment.partially_refunded |
| Typical case | Mistake, immediate cancel | Return, dispute, overcharge |
Eligibility is governed by the transaction's configured time window, not only by settlement state.
Attempting a refund too early can return 422 with a processor message such as
"Invalid transaction" - that text comes from the processor and is not a response ROKI guarantees.
Implement reversal as: try void first; if it is rejected as not voidable or already settled, refund instead.
13.3 Void
POST /api/connect/v1/payments/{transaction_id}/void
{ "reason": "Customer cancelled" } # optional, max 2000 chars
Returns the payment with status: "voided". Rejections arrive as 422 under errors.void:
already voided, already refunded (use refund), not in a voidable state, or already settled.
A void also sets the refund fields. After voiding, the payment carries
{ "status": "voided", "refunded_amount": 25, "refund_status": "full" }
even though nothing was refunded. So refund_status === 'full' does not mean "this was
refunded" - it is equally true of a void. Branch on status (voided vs refunded vs
partially_refunded) and treat the refund fields as the amount returned to the cardholder by any
means, not as evidence of which operation was used. Reporting that counts refunds by
refund_status will silently count voids as refunds, and the two are commercially different: a
void never reaches the customer's statement, a refund does.
The payment.voided webhook arrived about a second later on both transactions, carrying the same fields.
13.4 Refund
POST /api/connect/v1/payments/{transaction_id}/refund
{ "amount": 500.00, "reason": "Partial return" }
amount is required, minimum 0.01, up to the remaining refundable amount. A partial refund leaves
partially_refunded; a full one sets refunded. 422 under errors.amount (below minimum, above
remaining) or errors.refund (voided transaction, not refundable).
Refund works the same for a transaction created by any mode, including an embedded checkout or a
saved-card charge. There is no separate refund endpoint for token charges: use the id the charge
returned as the transaction_id here.
13.5 Receipts
GET /payments/{transaction_id}/receipt returns payment_id, transaction_id and a public
receipt_url. Add /download for the PDF. Receipts survive a void or refund as long as the original
charge still has a transaction.
13.6 A note on identifiers
Two different handles, and mixing them is the most common mistake:
- The numeric payment
idis forGET /payments/{id}. - The
transaction_idUUID is for void, refund and receipts.
Both are worth persisting when a payment is charged. Listing (9.2) can recover a lost id, but only if you know what you are looking for.
