Authorize now, capture later
Hold the funds when the customer orders, capture them for what you actually shipped, and read back the record that settles.
Signed in, you can run this blueprint against your own sandbox merchant one call at a time, with the values from each call threaded into the next. Sign in to run it.
4 steps, 3 API callsPaymentsTransactions
Samples use {{API_KEY}} for your API key and {{BASE_URL}} for this instance's API address. Anything else in double braces is a value an earlier step gave you.
Hold the funds
1. Authorize the card without taking the money
API call
POST /api/transactions
Send the same request you would send for a sale, with a transaction type of Authorization. The issuer holds the funds and nothing moves. The capture below takes the money, and an authorization nobody ever captures expires on the issuer's own schedule rather than yours. Authorize at the full order amount, because a capture can go down from here and can't go up.
Values this step gives you
{{transactionId}}The id of the authorization, from the response body's id property.{{merchantId}}The merchant the authorization belongs to, from the response body's merchantId property.
curl -X POST "{{BASE_URL}}/api/transactions" \
-H "api-key: {{API_KEY}}" \
-H "Content-Type: application/json" \
-d '{
"transactionType": "Authorization",
"cardData": {
"cardNumber": "4111111111111111",
"nameOnCard": "Jane Doe",
"expirationMonth": 12,
"expirationYear": 2030,
"cvv": 123
},
"invoiceData": {
"amounts": { "base": 10.00, "total": 10.00 }
}
}'using var http = new HttpClient { BaseAddress = new Uri("{{BASE_URL}}") };
http.DefaultRequestHeaders.Add("api-key", "{{API_KEY}}");
var response = await http.PostAsJsonAsync("/api/transactions", new
{
transactionType = "Authorization",
cardData = new
{
cardNumber = "4111111111111111",
nameOnCard = "Jane Doe",
expirationMonth = 12,
expirationYear = 2030,
cvv = 123
},
invoiceData = new
{
amounts = new { @base = 10.00m, total = 10.00m }
}
});
response.EnsureSuccessStatusCode();
var authorization = await response.Content.ReadFromJsonAsync<JsonElement>();
var transactionId = authorization.GetProperty("id").GetString();
var merchantId = authorization.GetProperty("merchantId").GetString();What this step answers with
Abridged to the properties this step depends on. A real response carries more.
{
"id": "9f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f",
"merchantId": "3a7b1c9d-2e4f-4a6b-8c8d-9e0f1a2b3c4d",
"transactionType": "Authorization",
"currentStage": "Authorized",
"resultCode": "Ok",
"authorizedAmount": 10.00,
"responseData": {
"resultCode": "Ok",
"resultMessage": "Approved"
}
}Capture what you shipped
2. Capture the authorization when you ship
API call
POST /api/transactions/by-merchant/{{merchantId}}/{{transactionId}}/operations
Capture for 9.00 against an authorization of 10.00. Capturing less than you held is ordinary rather than an edge case: a line went out of stock, or the shipping came in under the estimate. The network's rules release the unused part of the hold. Leave the amount out entirely to capture the whole authorization. Capture once and only once, because an authorization keeps no remainder to capture afterward, so a second shipment needs a second authorization. In production, send an idempotencyKey as well, so a retried request after a timeout can't capture twice.
Values this step gives you
curl -X POST "{{BASE_URL}}/api/transactions/by-merchant/{{merchantId}}/{{transactionId}}/operations" \
-H "api-key: {{API_KEY}}" \
-H "Content-Type: application/json" \
-d '{
"operationType": "Capture",
"amount": 9.00
}'var capture = await http.PostAsJsonAsync(
$"/api/transactions/by-merchant/{merchantId}/{transactionId}/operations",
new
{
operationType = "Capture",
amount = 9.00m
});
capture.EnsureSuccessStatusCode();
var result = await capture.Content.ReadFromJsonAsync<JsonElement>();
var succeeded = result.GetProperty("success").GetBoolean();What this step answers with
Abridged to the properties this step depends on. A real response carries more.
{
"success": true,
"operationType": "Capture",
"transactionId": "{{transactionId}}",
"timedOut": false
}Confirm what was captured
3. Read the transaction back
API call
GET /api/transactions/{{transactionId}}
Read the same transaction you authorized. A capture changes the record in place rather than creating a second one, so currentStage now reads Captured while authorizedAmount still holds what you originally authorized. Reconcile your order against this record rather than against the capture response. The capture response tells you whether the operation succeeded. The transaction is what settles.
Values this step gives you
curl "{{BASE_URL}}/api/transactions/{{transactionId}}" \
-H "api-key: {{API_KEY}}"var transaction = await http.GetFromJsonAsync<JsonElement>(
$"/api/transactions/{transactionId}");
var stage = transaction.GetProperty("currentStage").GetString();What this step answers with
Abridged to the properties this step depends on. A real response carries more.
{
"id": "{{transactionId}}",
"merchantId": "{{merchantId}}",
"transactionType": "Authorization",
"currentStage": "Captured",
"resultCode": "Ok",
"authorizedAmount": 10.00,
"responseData": {
"resultCode": "Ok",
"resultMessage": "Approved"
}
}If the amount can still grow
4. Raise the authorization rather than authorizing again
On your side
Raise the existing hold rather than adding a second card transaction alongside the first. A capture can only go down from the amount you authorized, and an order can still grow before you fulfill it: an added item, a tip, an extended stay. That's the IncrementalAuthorization operation type, on the same endpoint the capture above used. It sends a fresh authorization message to the network, so the issuer can refuse it, and not every processor supports it. The reference below documents it in full. This blueprint stops at the ordinary shipped-for-less flow.