This page is limited to a select group of individuals or a specific audience and is not yet available to the general public. Please get in touch with the product team before sharing this link.
At-Most-Once Processing
At-Most-Once Processing or Idempotency processing helps merchants ensure that duplicate API requests do not result in duplicate actions such as charging or refunding a payment multiple times. In contrast to transaction duplicate checking, this feature requires merchants to specify a unique request key, and the two features are mutually exclusive.
There are two primary categories of idempotency features:
- Network: a given request returns the same response, including network errors
- Logical Intent: a given request will run without causing incompatible, duplicative actions
Instead of network-level idempotency, Braintree has chosen to implement logical intent idempotency or, to avoid confusion, At-Most-Once Processing. This feature ensures that duplicate API requests do not result in duplicate actions, such as charging or refunding a payment multiple times. Duplicate requests do not reach payment networks and avoid overcharging merchants. Each identical request within a 30-day window will return the current state of that request's intent. This avoids unintended duplication and can safely retry requests when unexpected errors occur, for example, networking failures.
Details
At-Most-Once processing requires the merchant to provide an apiRequestKey
with each request to uniquely identify the logical action being attempted. Identical apiRequestKey
values within a 30-day window are considered duplicate requests. Once a request for a given apiRequestKey
has been received and partially processed, subsequent requests using the same apiRequestKey
are required to contain the same request parameters.
Request Parameter Processing
At the beginning of request processing SINGLE_USE
paymentMethodId
s are transformed into the contents of the referenced payment method. A hash of the transformed request parameters is then durably stored on a record of the request. Any Card Verification Values are ignored for the purposes of hash generation.
When a request's apiRequestKey
value matches a previously stored request record, request parameters are compared using the hash of the transformed request parameters.
Card Verification Values (CVVs)
Because of PCI security standards requirements, CVVs are processed differently from other parameters in several ways.
First, because each use of a CVV is limited to a specific usage and time window, CVVs are only available from tokenization while the payment method containing that CVV remains unconsumed and unexpired. Any retries subsequent to consumption of the payment method will not have access to the CVV. Therefore:
- Transactions succeeding on subsequent retry attempts may have downgraded interchange rates.
- Any merchant-configured CVV rules will be ignored when a CVV is not available on retry attempts.
Second, CVVs are ignored when determining if the request parameters on a retry attempt match the original request. This means that not only can an original request containing a CVV be retried with request parameters excluding a CVV, but also a retry may contain a CVV differing from the original request's parameters.
Duplicate Request Processing
When duplicate requests are encountered, the following are possible outcomes:
- If other request details, such as amount, customer, or payment method, are the same as the original request, and:
- The original request is still in progress, a retryable validation error is returned.
- The original request has been completed, the current state of that action's result is returned. For example, a charge request will return a transaction record, but the state may now be
SETTLED
instead ofSETTLING
(as well as error states such asPROCESSOR_DECLINED
).
- If other request details, such as amount, customer, or payment method, are not the same as the original request, a validation error is returned.
GraphQL Request Batching Compatibility
As noted in Request Parameter Processing, retry attempts with the same apiRequestKey
must have the same logical request parameters. Because the request parameters are compared logically, requests retrying a transaction with a newly tokenized SINGLE_USE
paymentMethodId
are treated the same as a request containing a different paymentMethodId
so long as both paymentMethodId
s were created using the same payment instrument details. Therefore, batched requests that, for example, both tokenize credit card details and then authorize against the tokenized PaymentMethod
function properly along with at-most-once processing.
Supported Actions
Currently, we support following actions
- Authorize
- Charge
- Submit For Partial Capture
- Submit For Settlement
- Credit
- Refund
Authorize
* authorizePaymentMethod
* authorizePayPalAccount
* authorizeVenmoAccount
* authorizeCreditCard
* authorizeInStoreCreditCard
Authorization (2-step) Scenarios (Excludes all capture scenarios)
Scenario | Request Key | Request Params | Transaction stored w/ 1st request? | Time since original request | Legacy API HTTP code | Outcome and response format | Notes |
---|---|---|---|---|---|---|---|
1 | New | Original | N/A | N/A | 200 or 201 | New Transaction object | |
2 | Dupe | Unchanged | Yes | Concurrent | 422 | Validation Error 915233 | |
3 | Dupe | Unchanged | Yes | < 30 days | 200 or 201 | Original Transaction object in current state (that state may not be successful) | |
4* | Dupe | Unchanged | No | < 30 days | 422 | Validation Error 915234 | Generate new ApiRequestKey ; see footnote |
5** | Dupe | Changed | Maybe | < 30 days | 422 | Validation Error 915232 | See footnote |
6 | Dupe | Unchanged | N/A | > 30 days | 200 or 201 | New Transaction object |
*Footnote for use case (4): Unlike capture scenarios (where a transaction record is already present), validation error 915234 on an auth can only be returned when no transaction record was stored. Because transaction records are durably persisted prior to attempting authorization with payment networks, the absence of a transaction record means reattempting the request cannot result in multiple authorizations.
*Footnote for use case (5): When receiving a validation error code 915232, it is the merchant’s responsibility to determine if generating a new ApiRequestKey
and sending changed request parameters would result in a functional duplicate charge. Specifically, the merchant should understand why the parameters have changed. A few overlapping scenarios are possible:
- The original request
- Could have been successful, resulting in an authorization.
- Could have been unsuccessful, with no pending authorization.
- The changed parameters
- Could be correct, with the
ApiRequestKey
being erroneously duplicative. For example, if the merchant has a bug in generatingApiRequestKey
values - Could be incorrect, with the
ApiRequestKey
being correct. For example, if the merchant had a bug that incorrectly mutated the parameters If a transaction record was created (whether or not the authorization was successful) the error response will contain an additional field with that transaction’s public identifier.
Charge
* chargePaymentMethod
* chargeUsBankAccount
* chargePayPalAccount
* chargeVenmoAccount
* chargeCreditCard
* chargeInStoreCreditCard
Charge (1-step) Scenarios
Scenario | Request Key | Request Params | Transaction stored w/ 1st request? | Time since original request | Legacy API HTTP code | Outcome and response format | Notes |
---|---|---|---|---|---|---|---|
1 | New | Original | N/A | N/A | 200 or 201 | New Transaction object | |
2 | Dupe | Unchanged | Yes | Concurrent | 422 | Validation Error 915233 | |
3 | Dupe | Unchanged | Yes | < 30 days | 200 or 201 | Original Transaction object in current state (that state may not be successful) | |
4* | Dupe | Unchanged | No | < 30 days | 422 | Validation Error 915234 | Generate new ApiRequestKey ; see footnote |
5** | Dupe | Changed | Maybe | < 30 days | 422 | Validation Error 915232 | See footnote |
6 | Dupe | Unchanged | N/A | > 30 days | 200 or 201 | New Transaction object |
*Footnote for use case (4) Multiple scenarios may lead to this outcome. If no transaction record was created, then retrying with a new ApiRequestKey
may be safely attempted. If a transaction record was created the error response will contain an additional key with the transaction's public identifier, and further investigation (and potentially intervention by Braintree) is required.
*Footnote for use case (5) Similarly to the footnote for the use case (5) for authorization scenarios, the merchant must understand why their integration has either reused an ApiRequestKey
or generated different request parameters. The error response will contain an additional key with the transaction's public identifier from the original request.
Refund
* refundTransaction
Scenario | Request Key | Request Params | Transaction stored w/ 1st request? | Time since original request | Legacy API HTTP code | Outcome and response format | Notes |
---|---|---|---|---|---|---|---|
1 | New | Original | N/A | N/A | 200 or 201 | New Transaction object | |
2 | Dupe | Unchanged | Yes | Concurrent | 422 | Validation Error 915233 | |
3 | Dupe | Unchanged | Yes | <30 days | 200 or 201 | Original Transaction object in current state (that state may not be successful) | |
4* | Dupe | Unchanged | No | <30 days | 422 | Validation Error 915234 | Generate new ApiRequestKey ; see footnote |
5** | Dupe | Changed | Maybe | <30 days | 422 | Validation Error 915232 | See footnote |
6 | Dupe | Unchanged | N/A | >30 days | undefined: depends on processor | undefined: depends on processor |
*Footnote for use case (4) Multiple scenarios may lead to this outcome. If the original transaction doesn't have a newly associated refund record retrying with a new ApiRequestKey
may be safely attempted. Otherwise, further investigation (and potentially intervention by Braintree) is required.
*Footnote for use case (5) Similarly to the footnote for the use case (5) for authorization scenarios, the merchant must understand why their integration has either reused an ApiRequestKey
or generated different request parameters. The error response will contain an additional key with the transaction's public identifier from the original request.
Submit For Settlement
* captureTransaction
Capture Scenarios (Excludes all authorization scenarios)
Scenario | Request Key | Request Params | Time since original capture request | Initial Capture Request Succeeded | Legacy API HTTP code | Outcome and response format | Notes |
---|---|---|---|---|---|---|---|
1 | New | Original | N/A | N/A | 200 or 201 | Updated Transaction object | |
2 | Dupe | Unchanged | Concurrent | N/A | 422 | Validation Error 915233 | |
3 | Dupe | Unchanged | < 30 days | Yes | 200 or 201 | Transaction object in the current state | |
4* | Dupe | Unchanged | < 30 days | No | 422 | Validation Error 915234 | See footnote |
5** | Dupe | Changed | < 30 days | Maybe | 422 | Validation Error 915232 | See footnote |
6 | Dupe | Unchanged | > 30 days | N/A | 200 or 201 | Updated Transaction object |
*Footnote for use case (4) Multiple scenarios may lead to this outcome. If the transaction status is still Authorized, retrying with a new ApiRequestKey
may be safely attempted. Otherwise, further investigation (and potentially intervention by Braintree) is required.
*Footnote for use case (5) Similarly to the footnote for the use case (5) for authorization scenarios, the merchant must understand why their integration has either reused an ApiRequestKey
or generated different request parameters. The error response will contain an additional key with the transaction's public identifier from the original request.
Submit For Partial Settlement
* partialCaptureTransaction
Scenario | Request Key | Request Params | Partial capture transaction stored w/ 1st request? | Time since original request | Legacy API HTTP code | Outcome and response format | Notes |
---|---|---|---|---|---|---|---|
1 | New | Original | N/A | N/A | 200 or 201 | Partial capture transaction object | |
2 | Dupe | Unchanged | Yes | Concurrent | 422 | Validation Error 915233 | |
3 | Dupe | Unchanged | Yes | < 30 days | 200 or 201 | Partial capture transaction object in current state (that state may not be successful) | |
4* | Dupe | Unchanged | No | < 30 days | 422 | Validation Error 915234 | Generate new ApiRequestKey ; see footnote |
5** | Dupe | Changed | Maybe | < 30 days | 422 | Validation Error 915232 | See footnote |
6 | Dupe | Unchanged | N/A | > 30 days | 200 or 201 | Partial capture transaction object |
*_Footnote for use case (4) Multiple scenarios may lead to this outcome. Because this request stores a new partial capture transaction record before attempting capture with payment networks, the absence of a partial capture transaction record means that it is safe to retry the request.
*Footnote for use case (5) Similarly to the footnote for the use case (5) for authorization scenarios, the merchant must understand why their integration has either reused an ApiRequestKey
or generated different request parameters. If a partial capture transaction record was created by an earlier request the error response will contain an additional key with the partial capture transaction's public identifier.
Credit
* refundCreditCard
* refundInStoreCreditCard
* refundUsBankAccount
Scenario | Request Key | Request Params | Refund stored w/ 1st request? | Time since original request | Legacy API HTTP code | Outcome and response format | Notes |
---|---|---|---|---|---|---|---|
1 | New | Original | N/A | N/A | 200 or 201 | New Refund transaction object | |
2 | Dupe | Unchanged | Yes | Concurrent | 422 | Validation Error 915233 | |
3 | Dupe | Unchanged | Yes | < 30 days | 200 or 201 | Original Refund transaction object in current state (that state may not be successful) | |
4* | Dupe | Unchanged | No | < 30 days | 422 | Validation Error 915234 | Generate new ApiRequestKey ; see footnote |
5** | Dupe | Changed | Maybe | < 30 days | 422 | Validation Error 915232 | See footnote |
6 | Dupe | Unchanged | N/A | > 30 days | 200 or 201 | New Refund transaction object |
*_Footnote for use case (4) Multiple scenarios may lead to this outcome. Because this request stores a new refund transaction record before attempting refund with payment networks, the absence of a refund transaction record means that it is safe to retry the request.
*Footnote for use case (5) Similarly to the footnote for the use case (5) for authorization scenarios, the merchant must understand why their integration has either reused an ApiRequestKey
or generated different request parameters. If a refund transaction record was created by an earlier request the error response will contain an additional key with the refund transaction's public identifier.
Error Codes
Error Code | Error Message |
---|---|
915232 | An ApiRequestKey may only be reused with parameters identical to the original request. |
915233 | A previous request with this ApiRequestKey is still in-flight. |
915234 | A previous request with this ApiRequestKey failed and cannot be retried. |