Cards: pre-authorization and capture
Pre-authorization lets you place a hold on funds on the customer's card without charging it immediately, and then capture all or part of the amount at a chosen time. You can cancel the unused hold. This is the standard model when the final amount or charge time is not known at the time of purchase.
POST/api/v1_0/cards/payment/{transactionId}/pay/card-pre-authPlace a hold on card funds without charging the card, with 3D Secure support.Full contract in the API Reference
POST/api/v1_0/cards/payment/{transactionId}/captureCapture all or part of the held funds.Full contract in the API Reference
POST/api/v1_0/cards/payment/{transactionId}/cancellationRelease the unused funds hold.Full contract in the API Reference
When to use it
- Reservations such as hotels and rentals: place a deposit hold and charge after the stay
- Charge on dispatch: charge only when the goods are ready to ship
- Variable amount: authorize with a margin and capture the final amount
- Marketplace: authorize now and settle after seller confirmation
You can also pre-authorize a saved token (card on file) by using the authorize_only field with
a recurring mandate alias; see Cards: recurring payments. This page describes
pre-authorization for a new card. Capture and cancellation work the same way for both variants.
How it works
The process has two phases:
- Pre-authorization places a hold on card funds without charging the card. The customer completes 3D Secure just as for a standard card payment.
- Capture OR cancellation either charges all or part of the held funds or releases the hold.
Pre-authorization uses the same card-data encryption and 3D Secure handling as Server-to-Server card payments. Read that guide first; this page describes only the differences.
Requirements
- An active Server-to-Server card integration, which requires PCI DSS certification and dpay approval; see Cards S2S
- Pre-authorization enabled for your Payment Point; contact dpay to enable it
Step 1: Register and pre-authorize the transaction
Register a transaction with transactionType: card_auth, retrieve the public key, and encrypt the
card data exactly as described in Cards S2S, steps 1–3. Only the payment endpoint
changes: use /pay/card-pre-auth instead of /pay/card-otp:
POST https://api-payments.dpay.pl/api/v1_0/cards/payment/{transactionId}/pay/card-pre-auth
Content-Type: application/json
Request parameters
They are identical to the parameters for /pay/card-otp:
{
"encryptedCardData": "Base64-encoded-encrypted-data...",
"deviceInfo": {
"browserAcceptHeader": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"browserJavaEnabled": "false",
"browserLanguage": "pl-PL",
"browserColorDepth": "24",
"browserScreenHeight": "1080",
"browserScreenWidth": "1920",
"browserTZ": "-60",
"browserUserAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)...",
"systemFamily": "Windows",
"geoLocalization": "52.2297,21.0122",
"deviceID": "device-unique-id",
"applicationName": "Chrome"
}
}
The deviceInfo object requires every field, including systemFamily, geoLocalization,
deviceID, and applicationName. Omitting any of them returns HTTP 422. See
Cards S2S for the complete specification.
Response
The response has the same structure as /pay/card-otp. The message.redirectType field
determines the next action:
redirectType | Meaning |
|---|---|
SUCCESS | Funds held successfully; proceed to capture or cancellation |
FORM | 3D Secure authentication required; render the form and retry with threeDsConfirmed: true |
Handle 3D Secure (FORM) exactly as described in Cards S2S. After successful
authorization, the funds are held but have not yet been charged. The value from transaction
registration is the maximum amount you can capture.
The issuing bank releases the funds hold after a certain period—usually several days to around two weeks, depending on the bank. Perform capture or cancellation before the hold expires.
Step 2a: Capture
Call the capture endpoint to charge the held funds. You can capture all or part of the authorization and perform multiple captures up to the authorized amount.
POST https://api-payments.dpay.pl/api/v1_0/cards/payment/{transactionId}/capture
Content-Type: application/json
Request parameters
| Field | Type | Required | Description |
|---|---|---|---|
service | string | Yes | Name of the service the payment belongs to |
amount | number | Yes | Amount to capture in PLN, for example 59.99; the sum of all captures cannot exceed the authorized amount |
checksum | string | Yes | SHA-256 checksum (see below) |
webhook | object | No | URL for the payment.captured event of this capture: url and optionally events (only payment.captured). It takes precedence over the URL from the payment registration and requires the service webhook secret - see Webhooks. It is not part of the checksum |
Checksum
A capture moves money, so you sign the request with the service Hash key:
sha256(capture|{service}|{transactionId}|{amount}|{hash})
captureis a fixed string - a capture checksum does not work as a cancellation checksum.transactionIdis the payment ID from the request URL (transactionIdfrom registration).- Compute
amountwith two decimal places, e.g.59.99or100.00.
Without a checksum or with an invalid one, the request fails with HTTP 401 before anything happens.
Example
curl -X POST https://api-payments.dpay.pl/api/v1_0/cards/payment/abc-def-123/capture \
-H "Content-Type: application/json" \
-d '{ "service": "abc123", "amount": 59.99, "checksum": "..." }'
$checksum = hash('sha256', implode('|', ['capture', $service, $transactionId, number_format($amount, 2, '.', ''), $hash]));
Response
{
"success": true,
"status": "success",
"message": { "redirectType": "SUCCESS" }
}
When the total captured amount reaches the full authorization amount, the transaction moves to the
captured state. Every capture, including a partial one, is also reported by the payment.captured
webhook event - a capture does not generate an IPN.
If the final amount is lower than the authorized amount—for example, because part of the order is unavailable—capture only the actual amount. You can capture the remainder with another call or cancel it as described below.
Step 2b: Cancel the authorization
Call the cancellation endpoint to release held funds without charging them. Cancellation is available only while the authorization has not been fully captured.
POST https://api-payments.dpay.pl/api/v1_0/cards/payment/{transactionId}/cancellation
Content-Type: application/json
Request parameters
| Field | Type | Required | Description |
|---|---|---|---|
service | string | Yes | Name of the service the payment belongs to |
amount | number | No | Amount to cancel in PLN. Omit it for full cancellation of the entire remaining uncaptured amount; provide it for partial cancellation |
checksum | string | Yes | SHA-256 checksum (see below) |
Checksum
sha256(cancellation|{service}|{transactionId}|{amount}|{hash})
For a full cancellation (without amount) the amount segment is empty, but the separators stay: sha256(cancellation|{service}|{transactionId}||{hash}). Compute the amount with two decimal places, as for capture.
Examples
Full cancellation, releasing the entire hold:
curl -X POST https://api-payments.dpay.pl/api/v1_0/cards/payment/abc-def-123/cancellation \
-H "Content-Type: application/json" \
-d '{ "service": "abc123", "checksum": "..." }'
Partial cancellation, releasing part of the hold:
curl -X POST https://api-payments.dpay.pl/api/v1_0/cards/payment/abc-def-123/cancellation \
-H "Content-Type: application/json" \
-d '{ "service": "abc123", "amount": 30.00, "checksum": "..." }'
Response
{
"success": true,
"status": "success",
"message": { "redirectType": "SUCCESS" }
}
Full cancellation closes the authorization. Further capture or cancellation calls are no longer possible.
Lifecycle and constraints
| Rule | Description |
|---|---|
Capture requires amount | A capture amount is required; the total captured amount must not exceed the authorization amount |
Cancellation amount is optional | No amount means full cancellation of the remaining amount |
| Order of operations | You can cancel only an active authorization, not one that has been fully captured, canceled, or finalized |
| Captured amount | It cannot be canceled; only the uncaptured remainder can be canceled |
| Expiration | The issuing bank releases an uncaptured hold when it expires |
Error handling
| Message | Cause | Action |
|---|---|---|
Missing service or checksum (HTTP 401, code CHECKSUM_REQUIRED) | The service or checksum field is missing | Sign the request with the service Hash key |
Invalid checksum (HTTP 401, code INVALID_CHECKSUM) | Wrong checksum, another service, or the checksum of another operation | Check the formula, the amount with two decimal places and the operation name |
Invalid webhook URL: ... (HTTP 400, code WEBHOOK_URL_INVALID) | The URL in the webhook object breaks the rules, the cause in reason | Use a public HTTPS URL on port 443 |
Generate the webhook secret ... (HTTP 400, code WEBHOOK_SECRET_MISSING) | A capture with a webhook object without the service webhook secret | Generate the secret in the service settings |
Invalid webhook object (HTTP 422, code WEBHOOK_INVALID) | Invalid webhook object structure or a type other than payment.captured | Fix the object according to the parameters table |
Invalid capture amount | Missing or invalid capture amount | Provide a positive amount |
Capture amount exceeds authorized amount | Total captures exceed the authorization | Reduce the amount |
Transaction cannot be captured in its current state. | Authorization already captured, canceled, or finalized | Check the transaction status |
Transaction cannot be cancelled in its current state. | Authorization already captured, canceled, or finalized | Check the transaction status |
Cancellation amount exceeds remaining authorized amount | Cancellation amount exceeds the remaining hold | Reduce the amount |
Missing authorization reference for capture. / ... for cancellation. | The transaction has no authorization reference and is not an active pre-authorization | Verify that the transaction is a valid, unsettled pre-authorization |
Capture was declined by the card issuer. / Cancellation was declined by the card issuer. | The issuer declined the capture or cancellation | Check the transaction state and retry if appropriate |
Other processing errors, such as a declined authorization or invalid card, are described in the
shared Cards S2S guide. The message value can vary by situation, so avoid matching
it too strictly.
Testing in sandbox mode
For a service with test mode enabled, dpay simulates the entire pre-authorization flow without contacting an external card processor. You can test the full pre-authorization → capture or cancellation path using the test card numbers.
| Step | Test-mode behavior |
|---|---|
card-pre-auth with a success card number | Authorization succeeds and the transaction waits for capture |
card-pre-auth with a decline card number | Authorization is declined, the transaction is canceled, and a simulated error is returned |
capture | The funds capture is simulated; after full capture, the transaction receives the captured status |
cancellation | Cancellation is simulated; the pre-authorization is closed with the canceled status |
The authorization scenario—success or decline—is selected from the card number contained in the
encrypted card data, just as for card-otp payments. Test numbers are listed in
Test environment.