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.
Create a payment intent with a hold
Section titled “Create a payment intent with a hold”Add one field to the standard create call:
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" }'| Field | Type | Required | Description |
|---|---|---|---|
capture_method | string | No | automatic (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 pays
Section titled “The customer pays”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:
| Field | Meaning |
|---|---|
capture_method | automatic or manual. |
authorized_at | When the hold was placed. |
authorization_expires_at | authorized_at + 7 days. |
cancellation_reason | Set once cancelled: voided or expired. |
The merchant_of_record.payment_intent.authorized webhook fires once when the
hold is placed. See payment events.
Capture
Section titled “Capture”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.
| Response | Cause |
|---|---|
400 Payment intent is not authorized | The intent is not in AUTHORIZED. |
400 Payment intent has already been captured | Second capture. |
400 Authorization has expired | Past the 7-day window. |
402 with error.failure_reason | The 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.
Cancel the authorization
Section titled “Cancel the authorization”Release the hold without charging:
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.
Expiry
Section titled “Expiry”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.
Lifecycle
Section titled “Lifecycle”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.