Pre-authorization, Capture & Void
With a pre-authorization the payment is not charged immediately: the buyer's bank holds the amount and the payment waits in AUTHORIZED. When you are ready to fulfil the order you capture all or part of the amount; if you will not fulfil it you void the hold.
Typical uses: charging when the order ships, deposits and reservations, partially fulfilled orders, payments held until a review finishes. The same hold, capture and void model is also used for the card parts of a split payment.
You request a pre-authorization with the preauth field of the payment request. This page describes every pre-authorization rule in one place; the payment pages show the field in their own requests and link here.
All amounts are integers in minor units (kuruş): "amount": 10050 = ₺100.50. Do not send decimals or floats.
How it works
Send preauth: true in the payment request
Which request carries the field depends on your payment path; see Requesting a pre-authorization.
The amount is held
When the payment succeeds its status becomes AUTHORIZED instead of CAPTURED and payment.authorized is sent to your webhook. No money has moved yet; do not ship the order on this event.
Capture or void
When you fulfil the order, capture the amount (payment.captured or payment.partially_captured). If you will not fulfil it, void the hold (payment.voided).
Do not miss the window
A pre-authorization that is not captured is closed on the platform as EXPIRED 7 days after the payment was created; see Expiry.
Requesting a pre-authorization
There is no separate pre-authorization endpoint. You send the preauth field in the request that starts the payment:
| Payment path | Request that carries preauth | Details |
|---|---|---|
| Checkout Form (card and non-card methods) | POST /payments/intents | The decision is stored on the intent; whatever charge the form makes (2D, 3D Secure or a non-card method) follows it. You send nothing else from the form. See Checkout Form |
| Direct API — card | POST /payments/direct/payment (paymentMethod: "CARD") | Card — 2D, Card — 3D Secure |
| Direct API — non-card methods | POST /payments/direct/payment | Payment |
The field behaves the same in both requests:
| Value | Result |
|---|---|
true | Pre-authorization: the amount is held, the payment becomes AUTHORIZED |
false or omitted | Immediate charge: the payment becomes CAPTURED |
Any non-boolean value ("true", "false", 1 …) | 400 VALIDATION_ERROR. A string value is not converted to a boolean |
A pre-authorization instruction is never silently turned into an immediate charge. The payment is never sent to a provider that cannot honour it; it is either routed to another suitable provider or rejected with an explicit error (see Which methods support it).
{
"amount": 10050,
"currency": "TRY",
"orderId": "ORD-84722",
"callbackUrl": "https://shop.example.com/payment/result",
"preauth": true
}For every field of POST /payments/intents see Checkout Form.
Which methods support it
The provider that processes the payment must support pre-authorization.
Card
- 2D: Providers that do not support pre-authorization are removed from the candidates and the payment is routed to one that does. If no suitable provider remains, the payment becomes
FAILEDwith decline codePREAUTH_NOT_SUPPORTED. - 3D Secure: The provider must also support pre-authorization over 3D Secure. Providers that do not are removed from the candidates. If no suitable provider remains, the payment becomes
FAILEDwith decline codePREAUTH_3DS_NOT_SUPPORTED. If no provider supports pre-authorization over 3D Secure at all, the request is rejected before the payment starts with422 payment.preauth.three_ds_unsupported. - In the 3D Secure flow no money is charged when the buyer completes the bank verification; the payment becomes
AUTHORIZED.
Non-card methods
- Whether a method and a provider support pre-authorization is shown by the
preauthfield of theGET /payments/methodsresponse (on the method row and on each provider). - If you send
preauth: truewith a method that does not support it, the request is rejected with422 payment.preauth.unsupported_for_method. The payment is not started and nothing is charged. - For a pre-authorization intent the Checkout Form only shows methods and providers that support pre-authorization.
Capture
POST /payments/{id}/capture charges the amount of an AUTHORIZED payment. The endpoint requires Step-Up (Bearer token and Ed25519 signature; see Authentication).
| Header | Value |
|---|---|
Idempotency-Key | Required. Without it the request is rejected with 400 payment.operation.idempotency_key_required. A request repeated with the same key does not capture twice |
| Field | Type | Required | Description |
|---|---|---|---|
paymentIntentId | string | Yes | The same payment ID as {id} in the path |
amount | integer | No | Amount to capture, minor units, at least 1. When omitted, the whole remaining amount is captured |
- When
amountis lower than the authorized amount a partial capture is made and the status becomesPARTIALLY_CAPTURED. You can capture the rest with another capture request; when the total reaches the authorized amount the status becomesCAPTURED. - The total captured can never exceed the authorized amount; a request that would exceed it is rejected with
400 payment.capture.amount_exceeded.
# See the Authentication page for how the Step-Up signature headers are computed.
curl -X POST https://api-gateway.lunixi.com/api/v1/payments/9c6f2b1a-4e8d-4a3f-b5e6-2d7c1a9b8e30/capture \
-H "Authorization: Bearer at_test_5f3e2b1a..." \
-H "Content-Type: application/json" \
-H "X-Key-Id: mk_test_sk_..." \
-H "X-Date: 2026-10-11T09:00:00.000Z" \
-H "X-Nonce: 3c1d8e2a-7b4f-4e6a-9d0c-5a2b1e8f7c64" \
-H "Digest: SHA-256=..." \
-H "X-Signature: ..." \
-H "Idempotency-Key: cap-ORD-84722-1" \
-d '{
"paymentIntentId": "9c6f2b1a-4e8d-4a3f-b5e6-2d7c1a9b8e30",
"amount": 7500
}'Capture response
{
"status": "success",
"code": "PAYMENT_PARTIALLY_CAPTURED",
"message": "…",
"data": {
"id": "9c6f2b1a-4e8d-4a3f-b5e6-2d7c1a9b8e30",
"amount": 10050,
"currency": "TRY",
"status": "PARTIALLY_CAPTURED"
},
"details": {
"capturedAmount": "7500",
"refundedAmount": "0",
"remainingCapturable": "2550",
"remainingRefundable": "7500"
},
"statusCode": 200,
"requestId": "…"
}On a full capture code is PAYMENT_CAPTURED and data.status is CAPTURED. The amounts in details are minor-unit strings.
Void
POST /payments/{id}/void removes a hold that has not been captured; no money moves and the payment becomes VOIDED. The endpoint accepts an API-key signature or a panel session; when calling it from the API, send the signature headers described on Authentication.
Idempotency-Keyis required; without it the request is rejected with400 payment.operation.idempotency_key_required.- The body is empty (
{}). - The response has
codePAYMENT_VOIDEDanddata.statusVOIDED.
# See the Authentication page for how the Step-Up signature headers are computed.
curl -X POST https://api-gateway.lunixi.com/api/v1/payments/9c6f2b1a-4e8d-4a3f-b5e6-2d7c1a9b8e30/void \
-H "Authorization: Bearer at_test_5f3e2b1a..." \
-H "Content-Type: application/json" \
-H "X-Key-Id: mk_test_sk_..." \
-H "X-Date: 2026-10-11T09:00:00.000Z" \
-H "X-Nonce: 6e2a9c41-0d3b-4f7a-8b15-9c4e2d1f7a03" \
-H "Digest: SHA-256=..." \
-H "X-Signature: ..." \
-H "Idempotency-Key: void-ORD-84722-1" \
-d '{}'Void response
{
"status": "success",
"code": "PAYMENT_VOIDED",
"message": "…",
"data": {
"id": "9c6f2b1a-4e8d-4a3f-b5e6-2d7c1a9b8e30",
"amount": 10050,
"currency": "TRY",
"status": "VOIDED"
},
"statusCode": 200,
"requestId": "…"
}Void is not a refund. Void removes a hold that has not been charged. To give back money that was charged, use Refunds; a void on a CAPTURED payment is rejected.
Expiry
- A pre-authorization that is neither captured nor voided becomes
EXPIRED(final status) on the platform 7 days after the payment was created, andpayment.expiredis sent. The window cannot be changed per account. - An
EXPIREDpayment can no longer be captured. - The hold on the card is not released by this event. No cancellation is sent to the bank; the hold drops off according to the bank's own period. To release the hold early, void it before the window ends.
Statuses
| Status | Meaning |
|---|---|
AUTHORIZED | The amount is held; nothing has been charged yet |
PARTIALLY_CAPTURED | Part of the held amount was charged; the rest can still be captured |
CAPTURED | The whole held amount was charged |
VOIDED | The hold was removed without a capture; no money moved |
EXPIRED | The pre-authorization was not captured within 7 days and was closed on the platform |
FAILED | The pre-authorization could not be obtained; nothing was held |
For every status see Listing Payments.
Errors
| Situation | Response | What to do |
|---|---|---|
preauth is not a boolean | 400 VALIDATION_ERROR | Send true or false |
| The non-card method does not support pre-authorization | 422 payment.preauth.unsupported_for_method | Use the method without preauth, or choose a method that supports it |
| No provider supports pre-authorization over 3D Secure | 422 payment.preauth.three_ds_unsupported | Request the pre-authorization with 2D, or take the payment as an immediate charge |
| The card provider does not support pre-authorization | Payment FAILED, decline code PREAUTH_NOT_SUPPORTED or PREAUTH_3DS_NOT_SUPPORTED | Nothing was held; take the payment as an immediate charge |
Capture or void without Idempotency-Key | 400 payment.operation.idempotency_key_required | Send a key with every capture and void |
| Capture amount exceeds the remaining amount | 400 payment.capture.amount_exceeded | Capture at most details.remainingCapturable |
The payment to capture is not AUTHORIZED or PARTIALLY_CAPTURED (e.g. EXPIRED, VOIDED) | 400 | Check the status with GET /payments/:id |
The payment to void is not in a voidable status (e.g. CAPTURED, VOIDED, EXPIRED) | 409 payment.intent.invalid_transition | Use Refunds for charged money |
| Another operation is running on the same payment | 409 payment.operation.locked | Retry shortly with the same Idempotency-Key |
The same Idempotency-Key was used with a different capture or void request | 409 payment.operation.idempotency_conflict | Use a new key for every separate operation; when repeating the same operation send the same key and the same body |
| Missing or invalid signature, expired token | 401, 403 | Sign requests as described on Authentication; for the codes see Error Handling |
Error codes
| Code | HTTP | Meaning |
|---|---|---|
payment.preauth.unsupported_for_method | 422 | The selected non-card method or provider does not support pre-authorization. The payment was not started and nothing was charged. details.paymentMethod and details.providerCode say which selection was rejected |
payment.preauth.three_ds_unsupported | 422 | A pre-authorization was requested for a 3D Secure payment but no provider supports pre-authorization over 3D Secure. The payment was not turned into an immediate charge |
payment.operation.idempotency_key_required | 400 | The Idempotency-Key header is required on this endpoint. Send the same key when repeating an operation and a new key for a new operation |
Webhooks
| Event | When |
|---|---|
payment.authorized | The amount is held (AUTHORIZED). No money has moved; do not ship on this event |
payment.partially_captured | A partial capture completed (PARTIALLY_CAPTURED) |
payment.captured | The whole held amount was charged (CAPTURED). Ship the order on this event |
payment.voided | The hold was removed without a capture (VOIDED) |
payment.expired | The pre-authorization was not captured within 7 days (EXPIRED). The hold on the card is not released by this event |
payment.failed | The pre-authorization could not be obtained (FAILED) |
For delivery and signature verification see Webhooks.
Next steps
- Checkout Form — creating the intent and the form
- Card — 2D and Card — 3D Secure — card payments
- Payment — every method from one endpoint
- Refunds — giving back charged money