Post Response Module
Some payments cannot be completed in a single server-to-server call. The issuer may need to challenge your customer, or the payment method may need to send them to an external provider. When that happens, the BR-DGE REST API returns a post-response action: a small instruction telling you that something must happen in the customer's browser before the payment can proceed.
The Post Response Module carries out that instruction for you. You hand it the action object exactly as your server received it, and the module renders the challenge, talks to the relevant provider, and resolves with the result you need to finish the payment.
Why this mattersEvery PSP implements 3-D Secure differently: different iframes, different tokens, different callbacks. This module absorbs those differences so your checkout code only ever deals with one flow. If you switch or add a PSP through Smart Routing, your front-end code should not need to change.
How it works
A post-response action is always a three-way conversation between your server, your customer's browser, and BR-DGE. Your server never handles the challenge itself — it only passes the action along.
sequenceDiagram
autonumber
participant Browser as Customer's browser
participant Server as Your server
participant BRDGE as BR-DGE REST API
participant PSP as PSP / Issuer
Browser->>Server: Submit checkout details
Server->>BRDGE: POST /v1/payments
BRDGE-->>Server: 202 Accepted, actionRequired: true, action: { ... }
Server-->>Browser: The action object
Browser->>Browser: handleAction(action)
Browser->>PSP: SDK performs the challenge
PSP-->>Browser: Authentication outcome
Browser-->>Server: Resolved result (code, nonce)
Server->>BRDGE: POST /v1/payments with the nonce
BRDGE-->>Server: 201 Approved, or another action
Server-->>Browser: Final outcome
In words:
- Your server creates a payment and receives a
202 Acceptedresponse containingactionRequired: trueand anactionobject. For 3-D Secure this arrives with response code2001. - Your server passes the
actionobject to your client. Nothing else from the response is needed. - Your client passes it to
handleAction. The module resolves with a result — typically anonceunder response code2002. - Your client sends that result to your server, which completes the payment using a
ProviderThreeDSecureNoncepayment instrument. - Depending on the PSP, step 4 may return another action. If it does, repeat from step 2.
Step 5 is not optionalA single payment can require more than one round of authentication. Build your integration as a loop that keeps calling
handleActionwhileactionRequiredistrue, rather than assuming one challenge is enough. Integrations that handle only the first action will fail intermittently on certain PSPs and card ranges.
For the wider context, see the 3-D Secure Payment Flow and Post-Response Actions.
Before you begin
You will need:
| Requirement | Notes |
|---|---|
| A Client API Key | Safe to expose in the browser. Never use a Server API Key in client code. |
| The Comcarde JavaScript Client | This module is a plugin and cannot be used on its own. |
A server that can relay the action object to your client | Any transport is fine: JSON response, template variable, WebSocket. |
The Web SDK is plain ES5 JavaScript. There is no build step, no bundler and no framework requirement — the scripts attach a global comcarde object to the page.
Promise support
handleActionreturns a Promise. If you support browsers without native Promise support, load a polyfill before the SDK scripts. If you are unsure whether your SDK version bundles one, ask support.
Install
Add the module script to your page, after the client script. Use the sandbox asset host while developing and the production host for live traffic.
<script src="https://sandbox-assets.comcarde.com/web/v2/js/client.min.js"></script>
<script src="https://sandbox-assets.comcarde.com/web/v2/js/post-response-action.min.js"></script><script src="https://assets.comcarde.com/web/v2/js/client.min.js"></script>
<script src="https://assets.comcarde.com/web/v2/js/post-response-action.min.js"></script>
Dedicated environmentsIf you have a dedicated BR-DGE environment, your asset host will differ from the two above. Contact support if you are unsure which to use.
Load order matters: client.min.js must be parsed before the module script runs, and both must be present before you call comcarde.client.create.
Quick start
The minimum viable integration. Create the client, create the module, hand it an action.
comcarde.client.create(
{
authorization: clientApiKey // your Client API Key
},
function (clientErr, clientInstance) {
if (clientErr) {
console.error('Client creation failed', clientErr);
return;
}
comcarde.postResponseAction.create(
{
client: clientInstance
},
function (err, postResponseActionInstance) {
if (err) {
console.error('Module creation failed', err);
return;
}
// The 'action' object from your server's payment response.
var action = actionFromServerResponse;
postResponseActionInstance
.handleAction(action)
.then(function (response) {
// The action completed. Inspect response.code to decide what to do next.
console.log(response.code, response.message);
})
.catch(function (handleErr) {
console.error('handleAction failed', JSON.stringify(handleErr));
});
}
);
}
);
Parse once, not twice
handleActionexpects a JavaScript object, not a JSON string. If your server sends the whole payment response as JSON and your client already parses it, passresponse.actionstraight through. Only callJSON.parseif you are handling a raw string, for example a value injected into a template:var action = JSON.parse(actionJsonFromServerResponse); // string in var action = paymentResponse.action; // already an objectCalling
JSON.parseon an object is a common cause ofTHREE_D_SECURE_ERROR_INVALID_ACTION_TYPE.
API reference
comcarde.postResponseAction.create(options, callback)
comcarde.postResponseAction.create(options, callback)Creates a module instance bound to a client instance.
| Parameter | Type | Required | Description |
|---|---|---|---|
options.client | Object | Yes | A client instance from comcarde.client.create. |
options.styles | Object | No | Presentation overrides for the challenge window. See Controlling the challenge z-index. |
callback | Function | Yes | Node-style callback, function (err, instance). |
The callback receives an error or an instance — always check err before using the instance. Errors follow the standard Web SDK error shape:
{
"code": "4001",
"message": "Access denied."
}Create the instance once when your checkout page loads and reuse it. You do not need a fresh instance per action.
instance.handleAction(action[, callbacks])
instance.handleAction(action[, callbacks])Executes a post-response action. Returns a Promise.
| Parameter | Type | Required | Description |
|---|---|---|---|
action | Object | Yes | The action object from the REST API response, unmodified. |
callbacks | Object | No | Progress callbacks. See Callbacks. |
| Outcome | Meaning |
|---|---|
| Resolves | The action ran to completion. This does not mean the payment succeeded — check response.code. |
| Rejects | The action could not be completed. See Error reference. |
The action object
Pass this through verbatim. Do not reshape it, add fields, or strip fields — the module uses type to select the correct PSP-specific handler.
{
"action": {
"type": "3D_SECURE",
"paymentId": "22669f90-5bf0-45df-ba8c-ea6d4235a5da",
"data": {
"clientToken": "aaabbb",
"verifyCardPayload": "payload"
}
}
}| Field | Type | Description |
|---|---|---|
type | String | The kind of action, for example 3D_SECURE. Determines how the module handles it. |
paymentId | String | The BR-DGE payment this action belongs to. Useful for correlating logs. |
data | Object | Opaque, PSP-specific payload. Contents vary; treat as a black box. |
Do not branch ondataThe keys inside
datadiffer between PSPs and may change as connections are added. Reading them in your own code couples your checkout to a specific PSP and defeats the purpose of the module.
The resolved response
| Field | Type | Description |
|---|---|---|
code | String | A BR-DGE response code describing the outcome. Branch on this. |
message | String | Human-readable explanation. Useful for logs; not intended for display to customers. |
paymentId | String | The payment this result belongs to. |
nonce | String | Present on 2002. Use this on your server to complete the payment. |
threeDSecureInfo | Object | Optional authentication detail. See below. |
The codes you are most likely to see:
Other codes are possible depending on the payment method and PSP, so write your handler with a default branch rather than assuming only the two above.
threeDSecureInfo
threeDSecureInfoSupplied when the PSP returns authentication detail. Always check the object exists before reading it.
| Field | Type | Description |
|---|---|---|
pspStatus | String | Status description as returned by the PSP. Raw, PSP-specific text — log it, don't parse it. |
threeDSecureVersion | String | The protocol version used, for example "2". |
liabilityShifted | Boolean | Whether liability for fraud has moved to the issuer. |
liabilityShiftPossible | Boolean | Whether the payment instrument was eligible for 3-D Secure at all. |
Callbacks
You can optionally pass functions that fire at points during execution, which is useful for managing spinners and re-enabling your pay button.
var callbacks = {
windowClosed: function () {
// The customer dismissed the challenge window.
hideSpinner();
enablePayButton();
},
actionCompleted: function () {
// The action finished; the promise is about to resolve.
hideSpinner();
}
};
postResponseActionInstance.handleAction(action, callbacks);| Callback | Fires when |
|---|---|
windowClosed | The challenge window or overlay is closed, including when the customer dismisses it. |
actionCompleted | The action has completed. |
Callback support is not universalNot every action type supports callbacks, and the set available depends on the action type and the PSP handling your payment. Treat them as progress hints, not guarantees — never rely on
actionCompletedas your only completion signal. Put the logic that must always run in.then()and.catch(). Contact support to confirm which callbacks apply to the actions configured on your account.
Controlling the challenge z-index
3-D Secure challenges are rendered in a modal window layered over your checkout page. If your own page has high-stacking elements — a sticky header, a cookie banner, a basket drawer, a loading overlay — they can appear on top of the challenge, or hide it entirely. The customer sees a blank or partially obscured overlay and abandons.
Pass threeDSModalZIndex in the styles object when creating the module to place the challenge window at a specific z-index:
comcarde.postResponseAction.create(
{
client: clientInstance,
styles: {
threeDSModalZIndex: 12000
}
},
function (err, postResponseActionInstance) {
if (err) {
console.error('Module creation failed', err);
return;
}
// ... handleAction as usual
}
);| Style | Type | Description |
|---|---|---|
threeDSModalZIndex | Number | Forces the 3-D Secure challenge window to render at the specified z-index. |
Set this to a value above the highest z-index on your checkout page. Audit what you actually use rather than guessing — third-party scripts such as chat widgets, consent managers, and session-recording tools frequently sit in the tens of thousands.
Configure it once
stylesis set when the module is created, not per action, so a single value covers every challenge on the page. Set it during your normal checkout initialisation rather than trying to adjust it when an action arrives.
z-index only competes within the same stacking contextIf an ancestor of the challenge creates its own stacking context — through
transform,filter,opacitybelow1,will-change, or its ownpositionplusz-index— raisingthreeDSModalZIndexwill not lift the challenge above elements outside that context. If a large value makes no difference, the problem is the stacking context, not the number. Inspect the ancestor chain before increasing it further.
Because this affects presentation only, it cannot cause an authentication failure — but it is a common cause of apparent ones, where the challenge renders correctly and the customer simply cannot see or interact with it. If you see an unexplained cluster of THREE_D_SECURE_METHOD_FAILURE errors or windowClosed callbacks, check this before escalating.
Worked example: 3-D Secure
This is the full pattern, including the liability-shift decision and the error path.
comcarde.client.create(
{
authorization: clientApiKey // your Client API Key
},
function (clientErr, clientInstance) {
if (clientErr) {
console.error('Client creation failed', clientErr);
return;
}
comcarde.postResponseAction.create(
{
client: clientInstance
},
function (err, postResponseActionInstance) {
if (err) {
console.error('Module creation failed', err);
return;
}
var action = paymentResponse.action;
postResponseActionInstance
.handleAction(action)
.then(function (response) {
var paymentId = response.paymentId;
if (response.code === '2002') {
// Authentication finished. A nonce is available to complete the payment.
var info = response.threeDSecureInfo;
if (info && info.liabilityShifted) {
// Authentication succeeded, or the issuer does not support 3-D Secure
// while the payment method does. Either way, fraud liability now sits
// with the issuer. Complete the payment on your server.
completePaymentOnServer(paymentId, response.nonce);
return;
}
// Liability has NOT shifted. You may still complete the payment, but you
// must set threeDSecureRequired to false on the follow-up request, and
// you carry the fraud risk. This is a commercial decision.
if (info && info.liabilityShiftPossible) {
// Instrument was eligible for 3-D Secure but authentication did not
// succeed. Higher risk.
} else {
// Instrument was never eligible for 3-D Secure.
}
handleUnauthenticatedPayment(paymentId, response.nonce, info);
return;
}
if (response.code === '2004') {
// An error occurred during authentication.
showRetryableError();
return;
}
// Any other code: log it and fail safe rather than silently continuing.
console.warn('Unexpected code from handleAction', response.code, response.message);
showGenericError();
})
.catch(function (handleErr) {
// handleErr usually carries a 3-D Secure action error code explaining what
// went wrong. Include it verbatim in any support request.
console.error(
'Exception while processing 3-D Secure action: ' + JSON.stringify(handleErr)
);
showGenericError();
});
}
);
}
);Deciding what to do when liability has not shifted
liabilityShifted: false is not automatically a decline. It is a risk decision only you can make.
liabilityShifted | liabilityShiftPossible | Interpretation | Typical action |
|---|---|---|---|
true | — | Authentication succeeded, or the issuer does not participate while the instrument does. Liability sits with the issuer. | Complete the payment. |
false | true | The instrument was eligible for 3-D Secure, but authentication did not succeed. | Highest risk. Most merchants decline, or retry authentication. |
false | false | The instrument was never eligible for 3-D Secure. | Complete only if you accept the fraud risk. |
To complete a payment where liability has not shifted, set threeDSecureRequired to false on the follow-up request. Otherwise it will be rejected.
Regulatory contextProceeding without a liability shift may conflict with Strong Customer Authentication obligations in your market. Confirm your position before enabling this path in production.
Error reference
When handleAction rejects, the error usually carries one of the codes below. Log the entire error object — not just the code — and include it in any support request along with the paymentId.
General 3-D Secure errors
| Code | Description | Where to look first |
|---|---|---|
THREE_D_SECURE_ERROR | A problem occurred during the 3-D Secure verification process. | Generic. Raise with support, quoting the paymentId. |
THREE_D_SECURE_METHOD_FAILURE | No nonce was received during the 3-D Secure verification process. | The challenge did not complete. Check whether the customer abandoned it, or whether the window was blocked. |
THREE_D_SECURE_ACTION_REQUIRED | A problem occurred during the 3-D Secure verification process. | Generic. Confirm you passed the full action object. |
THREE_D_SECURE_ACTION_INVALID | A problem occurred during the 3-D Secure verification process. | Generic. Usually a malformed or reshaped action object. |
THREE_D_SECURE_UNSUPPORTED_PSP | The PSP for this action is not supported by this version of the SDK. | Your SDK version is behind. Update to the latest asset URL and retest. |
THREE_D_SECURE_ERROR_NO_RESPONSE | The server did not respond in time. May also indicate a bad request from the issuer's ACS. | Transient in many cases. Check for network restrictions or content security policy rules blocking the ACS domain. |
THREE_D_SECURE_ERROR_INVALID_ORIGIN | This origin is not allowed. | Check the origin you submitted on the payment request matches the page actually hosting the challenge. |
On the generic descriptionsThree of the codes above share the same description. They are distinct conditions internally, so quote the exact code when contacting support — it materially speeds up diagnosis.
Malformed action errors
These indicate the action object reaching handleAction is not what the module expected. In almost every case the cause is in the relay between your server and your client: partial serialisation, an object being stringified twice, or only part of the payload being forwarded.
| Code | Description |
|---|---|
THREE_D_SECURE_ERROR_INVALID_ACTION_TYPE | An invalid action type was provided. |
THREE_D_SECURE_ERROR_MISSING_PROPERTY_ACS_URL | The required acsUrl property is missing. |
THREE_D_SECURE_ERROR_MISSING_PROPERTY_TOKEN | The required token property is missing. |
THREE_D_SECURE_ERROR_MISSING_PROPERTY_JWT | The required jwt property is missing. |
PSP-specific errors
| Code | PSP | Description |
|---|---|---|
BARCLAYCARD_THREE_D_SECURE_HTML_ERROR | Barclaycard Smartpay Fuse | The htmlAnswer was invalid. |
STRIPE_THREE_D_SECURE_ERROR_FAILED_TO_LOAD_SDK | Stripe | Failed to load the Stripe SDK. |
TRUST_THREE_D_SECURE_ERROR_FAILED_TO_LOAD_SDK | Trust Payments | Failed to load the Trust Payments SDK. |
NUVEI_THREE_D_SECURE_METHOD_PAYLOAD | Nuvei | methodPayload object missing from threeDSecureAction. |
NUVEI_THREE_D_SECURE_C_REQ | Nuvei | cReq object missing from threeDSecureAction. |
ADYEN_THREE_D_SECURE_MISSING_POST_URL | Adyen | URL missing from the payment response. |
ADYEN_THREE_D_SECURE_MISSING_PA_REQ | Adyen | PaReq object missing from threeDSecureAction. |
ADYEN_THREE_D_SECURE_MISSING_MD | Adyen | MD object missing from threeDSecureAction. |
The two FAILED_TO_LOAD_SDK errors mean a third-party script could not be fetched from the customer's browser. Check your content security policy allows the PSP's script domains.
Troubleshooting
| Symptom | Likely cause |
|---|---|
comcarde.postResponseAction is undefined | The module script did not load, or ran before client.min.js. Check load order and the network tab. |
| Rejects immediately with a missing-property error | The action object was reshaped, double-parsed, or only partially forwarded from your server. Log it on both sides and compare. |
| Challenge window never appears | A popup or iframe was blocked. Call handleAction from within the customer's click handler rather than after an intervening await or timeout. |
| Challenge appears behind your header, overlay or cookie banner | Stacking order. Set threeDSModalZIndex above the highest z-index on your page. |
| Challenge is visible but unclickable, or shows as a blank overlay | Another element is layered on top of it. Same fix as above; if raising the value changes nothing, check for an ancestor creating its own stacking context. |
| Payment succeeds in sandbox but fails live | Mismatched hosts. Confirm the asset host, API subdomain and API key all belong to the same environment. |
| Works on one card, fails on another | Different card ranges route to different PSPs. Check your integration handles repeat actions and does not read PSP-specific fields from data. |
THREE_D_SECURE_METHOD_FAILURE under load | Often customer abandonment rather than a fault. Correlate against your windowClosed callback before escalating — and rule out a z-index problem, which looks identical in your metrics. |
Some PSPs require additional fields on the original payment request before 3-D Secure will work at all — in particular browserData, channel and origin. If authentication fails consistently for a given PSP, confirm those are populated before investigating the client.
Testing
Use the sandbox asset host and API subdomain together, and drive challenges with the simulator PSPs.
Worth covering explicitly before you go live:
- A frictionless authentication that resolves with
2002and a liability shift. - A challenge rendered on your real checkout page, with headers, banners and any third-party widgets present, to confirm the z-index is high enough. A challenge that renders fine in isolation can still be buried in production.
- A challenge the customer abandons, to verify your
windowClosedhandling and that your pay button becomes usable again. - A payment requiring two consecutive actions, to prove your loop works.
- A
2004processing error, to verify your error path.
PCI compliance in sandboxUse only mock payment instrument data against the sandbox. Never submit real cardholder data to a test environment.
Related documentation
- Web SDK Introduction — creating the client and handling SDK errors
- Post-Response Actions — the server-side view
- 3-D Secure Payment Flow — end-to-end sequence
- POST /v1/payments — see the
Payment with ProviderThreeDSecureNonceexample for completing an authenticated payment - Response Codes — full code list
- Auto 3-D Secure Step-up — automatic step-up behaviour
- Support — include the
paymentIdand the full error object
Updated 3 minutes ago
