SDK errors
captureCDR() rejects rather than returning a partial result. If it resolves, the evidence is stored.
Errors are ExpressConsentError instances carrying a code, a message, and sometimes details.
Every one is also dispatched on window as an expressconsent:error event, except
CAPTURE_NOT_IN_BROWSER, where there is no window to dispatch on.
try {
await window.ExpressConsent.captureCDR();
} catch (error) {
console.error(error.code, error.message, error.details?.backendCode);
}Handle them the same way
Whatever the cause, the response is the same: log it and let the person's submission proceed. We do not advise blocking a consumer from submitting because evidence capture failed.
Codes
| Code | Cause |
|---|---|
CONFIG_CID_MISSING | No organization identifier: no data-ec-cid, no ?cid= on the script URL, nothing on window.__ExpressConsentConfig, and no cid passed to the call. A setup error; it will fail on every capture. |
UPLOAD_FAILED | The server rejected the upload. details.backendCode says why; see below. |
CAPTURE_TIMEOUT | The call reached its time limit: your timeoutMs, or 130 seconds when you set none. details.timeoutMs is the limit. The upload continues in the background and the record can still be stored, but you get no cdrId for it. |
INTERNAL_ERROR | The capture failed locally: neither upload endpoint could be reached or gave a usable answer, or the page could not be serialized. |
INVALID_INPUT | An option has an invalid value, such as a devMode that is not a boolean, or a checkout capture still contains a card number. Nothing was uploaded. |
CONFIG_API_BASE_UNRESOLVED | The upload endpoint could not be resolved, because the SDK was not served from an ExpressConsent host. Load it from sdk.expressconsent.com; a self-hosted, proxied, or bundled copy cannot work out where to upload. |
CAPTURE_NOT_IN_BROWSER | No window or document. You are calling it during server-side rendering. |
NETWORK_ERROR appears in the exported ExpressConsentErrorCode type but is never thrown. Network
failures arrive as INTERNAL_ERROR.
error.details carries backendCode and backendMessage on UPLOAD_FAILED, and timeoutMs on
CAPTURE_TIMEOUT. Route backendCode into your monitoring; it is the only thing that says why an
upload was refused.
If the bundle has not run, window.ExpressConsent is undefined and the
property access throws a TypeError, which the same try/catch handles.
The same happens when the script has not yet finished loading: with async, a
very early submission can beat it. Guard with if (window.ExpressConsent) if
your form can be submitted in the first moments of page load.
Upload rejection reasons
When the code is UPLOAD_FAILED, details.backendCode carries the server's reason.
backendCode | Meaning |
|---|---|
CAPTURE_DISABLED | Capture is switched off for your organization. Contact us. |
PAYLOAD_TOO_LARGE | Over the 3 MiB limit. Usually inlineAssets left on in production, or an unusually large page. |
PAYLOAD_TOO_SMALL, INTEGRITY_FAILURE | The upload arrived empty, cut short, or changed. You do not see these: the SDK sends the same record again in a text-safe encoding, then tries the other endpoint, and reports INTERNAL_ERROR only if every attempt fails. It also keeps a copy in the browser to send again later. |
IDEMPOTENCY_CONFLICT | Different content was uploaded under a record identifier that already exists. |
MISSING_HEADER, INVALID_HEADER | A malformed request. Despite the names these are about the upload's query parameters, HTTP method, and content type rather than HTTP headers. Indicates a bug, or a proxy rewriting the upload. |
MISSING_COMMIT | A stored record was re-sent but its chain of custody could not be established, so it was refused. |
SERVER_ERROR | Our side failed. Retried automatically. |
INVALID_ENVELOPE, INVALID_SNAPSHOT | Defined but not currently returned. |
What to do about a record you never got
A capture that timed out or failed locally can still arrive later: from the upload that continued in
the background, or re-sent from the browser on the person's next visit. By then your code has moved
on without a cdrId.
Pass your own identifier in custom on every capture, and reconcile from the webhook or by filtering
the API on that value.