Skip to content

Authorize & Capture

Manual capture holds funds on the customer’s card when they pay, and settles them later with a separate capture call. Use it when the final charge is confirmed after the customer pays — check-in guarantees, security deposits, bookings that need review before you take the money.

Manual capture works for card payment types only (Card Payment, Virtual Card, MOTO). Captures are always for the full authorized amount; capture less by capturing in full and refunding the difference.

Add one field to the standard create call:

Terminal window
curl -X POST "https://staging.turnstay.com/api/v1/payments/intent" \
-H "Authorization: Bearer sk_test_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"account_id": 123,
"billing_amount": 100000,
"billing_currency": "ZAR",
"payment_type": "Card Payment",
"capture_method": "manual",
"merchant_reference": "BOOKING-2026-001"
}'
FieldTypeRequiredDescription
capture_methodstringNoautomatic (default) or manual.

Requests with a publishable key cannot set capture_method: manual; use your secret key. Unsupported payment types return a 400 with the reason.

The customer completes payment as normal. Instead of settling, the intent moves to AUTHORIZED: the funds are held on their card, but no money has moved. The intent gains these fields:

FieldMeaning
capture_methodautomatic or manual.
authorized_atWhen the hold was placed.
authorization_expires_atauthorized_at + 7 days.
cancellation_reasonSet once cancelled: voided or expired.

The merchant_of_record.payment_intent.authorized webhook fires once when the hold is placed. See payment events.

Terminal window
curl -X POST "https://staging.turnstay.com/api/v1/payments/intent/capture" \
-H "Authorization: Bearer sk_test_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{ "payment_intent_id": 45678 }'

On success the intent is PROCESSED and behaves like any other successful payment: receipt email, settlement, payouts, and the merchant_of_record.payment_intent.succeeded webhook. Payout timing counts from the capture, not the authorization.

ResponseCause
400 Payment intent is not authorizedThe intent is not in AUTHORIZED.
400 Payment intent has already been capturedSecond capture.
400 Authorization has expiredPast the 7-day window.
402 with error.failure_reasonThe card issuer or provider refused the capture.

After a 402 the intent stays AUTHORIZED and the capture can be retried, unless the hold no longer exists at the provider — then the intent moves to CANCELLED with the matching cancellation_reason.

Release the hold without charging:

Terminal window
curl -X POST "https://staging.turnstay.com/api/v1/payments/intent/cancel-authorization" \
-H "Authorization: Bearer sk_test_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{ "payment_intent_id": 45678 }'

On success the intent is CANCELLED with cancellation_reason: "voided", the hold disappears from the customer’s card, and the merchant_of_record.payment_intent.cancelled webhook fires.

Refunds are rejected while an intent is AUTHORIZED — there is nothing to refund, since no money has moved. Cancelling the authorization is the only way to undo a hold before capture. A cancelled payment link can be paid again.

Holds last 7 days. An authorization that is neither captured nor cancelled by authorization_expires_at is released automatically: the intent moves to CANCELLED with cancellation_reason: "expired" and the .cancelled webhook fires. Capture attempts past the deadline return 400 Authorization has expired.

Cancel holds you will not capture rather than letting them expire — the customer sees their funds released sooner.

create (manual) → INITIALIZED → AUTHORIZED → capture → PROCESSED → (refund…)
|
|→ cancel-authorization → CANCELLED (voided)
|→ 7 days pass → CANCELLED (expired)

If your integration filters on payment statuses, add AUTHORIZED and CANCELLED to your handling — they appear in list and count endpoints and in webhook payloads. Automatic-capture intents never enter either state.