Preauthorize and capture
To enable card-on-file features such as this one, including partial captures, you must contact Jetpay support directly.
This flow lets you authorize a saved card without capturing funds right away, then capture the amount later in one go or across several partial captures.
It suits platforms that need to reserve funds and settle them later. For example, rentals, deposits, usage-based billing, split shipments, or marketplaces confirming fulfillment.
If you want to charge a saved card immediately, see Directly charge a credit cardDirectly charge a credit card instead.
Prerequisites
- Getting your API KeyGetting your API Key
- Your Getting your bank account IDGetting your bank account ID where funds will be deposited
- Tokenize a contact credit cardTokenize a contact credit card
Before you preauthorize a saved card, the customer must exist as a Contact in Jetpay and have at least one tokenized payment method attached.
If you have not yet created a contact or collected card details, complete the Create a contactCreate a contact and Tokenize a contact credit cardTokenize a contact credit card guides first.
Flow overview
sequenceDiagram
autonumber
participant Platform
participant Jetpay
participant CardNetwork as Card Network
Platform->>Jetpay: POST /contact/{contact_id}/creditCards/{credit_card_id}/preauthorize
Jetpay->>CardNetwork: Authorize only
CardNetwork-->>Jetpay: Authorization result
Jetpay-->>Platform: Debit created (state: preauthorized)
loop One or more captures
Platform->>Jetpay: POST /debit/{debit_id}/captures
Jetpay->>CardNetwork: Capture request
Jetpay-->>Platform: Capture created (201)
Jetpay-->>Platform: Webhook (payments.captures.created)
end
opt Remaining balance not needed
Platform->>Jetpay: POST /debit/{debit_id}/release (or final_capture: true)
Jetpay->>CardNetwork: Release remaining authorization
Jetpay-->>Platform: Webhook (payments.preauthorizations.released)
endImplementation details
Create a preauthorization
curl https://extapi.demo.jetpay.baselinepayments.com/contact/contact_123/creditCards/cc_456/preauthorize \
-X POST \
-H "Authorization: Bearer <YOUR_API_TOKEN>" \
-H "Content-Type: application/json" \
-d '{
"amount": 100.00,
"to_bank_account_id": "<your_bank_account_id>",
"statement": "Test Statement"
}'Handle the response
If the card is successfully preauthorized, the endpoint returns HTTP 200:
{
"debit_id": "debit_123",
"amount": 100.00,
"statement": "Test Statement",
"to_bank_account_id": "your_bank_account_id",
"contact_id": "contact_123",
"from_credit_card_id": "cc_456",
"transaction_state": "preauthorized"
}Store the returned debit_id for the steps that follow.
Capture part of the amount
Endpoint: POST /debit/{debit_id}/captures
Capture as much of the preauthorized amount as you need. You can capture again later, up to the full amount.
Include an Idempotency-Key header whenever the body has capture_amount or final_capture. Without it, the request is refused with a 400.
curl https://extapi.demo.jetpay.baselinepayments.com/debit/debit_123/captures \
-X POST \
-H "Authorization: Bearer <YOUR_API_TOKEN>" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-789-shipment-1" \
-d '{
"capture_amount": 40.00
}'Handle the response
{
"identifier": "debit_123",
"state": "preauthorized",
"capture_id": "capture_001",
"capture_status": "paid",
"capture_amount": 40.00,
"principal_captured": 40.00,
"captured_amount": 41.20,
"available_amount": 60.00,
"final_capture": false,
"release": null
}Make the final capture
When you're done capturing, mark the last capture as final. Any remaining balance is released for you.
curl https://extapi.demo.jetpay.baselinepayments.com/debit/debit_123/captures \
-X POST \
-H "Authorization: Bearer <YOUR_API_TOKEN>" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-789-shipment-2" \
-d '{
"capture_amount": 35.00,
"final_capture": true
}'{
"identifier": "debit_123",
"state": "preauthorized",
"capture_id": "capture_002",
"capture_status": "paid",
"capture_amount": 35.00,
"principal_captured": 75.00,
"captured_amount": 77.25,
"available_amount": 25.00,
"final_capture": true,
"release": {
"requested": true,
"amount": 25.00,
"state": "pending",
"reason": null
}
}To capture the full amount in one step, call the same endpoint with no body.
Release the remaining balance
Endpoint: POST /debit/{debit_id}/release
If you won't capture anything more and didn't send a final capture, release the rest of the hold.
curl https://extapi.demo.jetpay.baselinepayments.com/debit/debit_123/release \
-X POST \
-H "Authorization: Bearer <YOUR_API_TOKEN>" \
-H "Idempotency-Key: order-789-release"{
"identifier": "debit_123",
"state": "release_requested",
"released_amount": 60.00,
"principal_captured": 40.00,
"captured_amount": 41.20,
"available_amount": 0.00,
"capture_count": 1,
"release": null
}List captures
Endpoint: GET /debit/{debit_id}/captures
See every capture on a debit, along with its running totals. To fetch one capture, use GET /debit/{debit_id}/captures/{capture_id}.
curl https://extapi.demo.jetpay.baselinepayments.com/debit/debit_123/captures \
-H "Authorization: Bearer <YOUR_API_TOKEN>"{
"transaction_id": "debit_123",
"currency": "CAD",
"principal_amount": 100.00,
"preauthorized_amount": 103.00,
"principal_captured": 40.00,
"captured_amount": 41.20,
"available_amount": 60.00,
"released_amount": 0.00,
"capture_count": 1,
"state": "preauthorized",
"is_capturable": true,
"captures": [
{
"capture_id": "capture_001",
"capture_status": "paid",
"capture_amount": 40.00,
"customer_surcharge_amount": 1.20,
"total_charged_amount": 41.20,
"created_at": "2026-09-28T15:04:05Z",
"estimated_deposit_date": "2026-09-30",
"is_final": false
}
]
}Track status updates via events/webhooks
As captures and the preauthorization move through their lifecycle, an Event is created for each update. The most efficient way to track these updates in your system is to register for webhooks. If you are not yet familiar with our webhooks system, please review the Events and webhooksEvents and webhooks guide.
See the Transactions reference for request fields, error codes and limits, and the Events reference for the capture and preauthorization events.
Handling failures
The preauthorization is declined. The request returns 422 and no debit is created. Ask the customer for another card, or try the same card again later.
A capture is refused. Nothing was captured. The error code in the response tells you why, and the fix is usually one of these:
- The amount is more than what's still available. List captures to get the current balance.
- Partial captures aren't enabled on your account. Capture the full amount, or contact Jetpay support.
- The preauthorization has hit its capture limit, already has a final capture, has been released, or has expired. It can't take more captures. Release what's left, or charge the card again with a new payment.
A capture request times out or returns a 5xx. Retry with the same Idempotency-Key. If the first attempt went through, you get the original capture back instead of a second one.
The Idempotency-Key is missing. A capture with capture_amount or final_capture but no Idempotency-Key header is refused with a 400. Add the header and send it again.
The Idempotency-Key is reused for a different capture. Each key belongs to the request it was first used with. Sending the same key with a different body is refused:
POST /debit/debit_123/captures
Idempotency-Key: order-789-shipment-1
{ "capture_amount": 40.00 }
→ 201, capture_001
POST /debit/debit_123/captures
Idempotency-Key: order-789-shipment-1
{ "capture_amount": 35.00 }
→ 422
{
"error": "idempotencyKeyMismatch",
"message": "Idempotency-Key was reused with a different request body."
}Use a new key for every new capture, and reuse a key only when retrying the exact same request.
A capture fails after it was accepted. You receive payments.captures.failed. The failed amount becomes available again, so you can retry it as a new capture with a new Idempotency-Key. Handle this event even if you never received payments.captures.created for that capture.
A final capture fails. The pending release is cancelled and the preauthorization reopens. Capture again, or release the balance yourself.
A release doesn't go through. The debit returns to preauthorized and no event is sent. If you haven't received payments.preauthorizations.released within the time you expect, check the debit's state and call release again with a new Idempotency-Key.
The preauthorization expires. You receive payments.preauthorizations.expired. Anything you already captured is kept, and the rest is freed. To collect more, charge the card again.
Testing
Use the demo environment to run the full flow before going live. Partial captures are off by default, so ask Jetpay support to enable them on your demo account first.
- Preauthorize a card, then capture part of the amount.
- Send the same capture again with the same Idempotency-Key and confirm you get the same capture_id back.
- Try to capture more than is available and confirm you handle the refusal.
- Make a final capture, then confirm you receive payments.preauthorizations.released.
- Preauthorize again and release the whole amount without capturing.
Declined captures, failed releases and expiry can't be triggered on demand, so cover those paths with unit tests against the example events.
Best practices
- Send an Idempotency-Key on every capture and release. It's required for partial and final captures, and it makes every retry safe. Reuse the key when retrying the same request, and use a new key for each new capture.
- Finish with a final capture. It frees the customer's remaining balance as soon as you're done, instead of holding it until expiry.
- Capture well before expiry. A preauthorization lasts about 7 days. Don't plan captures for the last day.
- Don't hard-code the capture limit. Read is_capturable from List captures to know whether another capture will be accepted.
- Store both IDs. Keep the debit_id and every capture_id so you can match webhook events to your records.
- Drive your status from webhooks. Update your records from events rather than polling.
FAQ
How long does a preauthorization last? About 7 days. After that it expires and any uncaptured amount is freed.
Can I capture more than I preauthorized? No. Captures can add up to the preauthorized amount at most. To charge more, create a new payment.
Do I need partial captures enabled to use this flow? Only for partial captures. Capturing the full amount in one step works without it.
How is the customer surcharge handled across captures? Each capture charges its share of the surcharge, so capturing the full amount charges exactly the preauthorized total.
What if I never capture? The preauthorization expires on its own. Release it as soon as you know you won't capture, so the customer's funds aren't held longer than needed.
Can I refund a preauthorization? You can refund what has been captured, even while the rest is still open. See Refund a credit card transactionRefund a credit card transaction. To give back an amount you haven't captured, release it.
What happens if I send two captures at the same time? Both are checked against the same balance, so you can never capture more than the preauthorized amount. If the second one would exceed it, it's refused.