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.

javascript
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

CodeCause
CONFIG_CID_MISSINGNo 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_FAILEDThe server rejected the upload. details.backendCode says why; see below.
CAPTURE_TIMEOUTThe 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_ERRORThe capture failed locally: neither upload endpoint could be reached or gave a usable answer, or the page could not be serialized.
INVALID_INPUTAn 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_UNRESOLVEDThe 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_BROWSERNo 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.

Upload rejection reasons

When the code is UPLOAD_FAILED, details.backendCode carries the server's reason.

backendCodeMeaning
CAPTURE_DISABLEDCapture is switched off for your organization. Contact us.
PAYLOAD_TOO_LARGEOver the 3 MiB limit. Usually inlineAssets left on in production, or an unusually large page.
PAYLOAD_TOO_SMALL, INTEGRITY_FAILUREThe 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_CONFLICTDifferent content was uploaded under a record identifier that already exists.
MISSING_HEADER, INVALID_HEADERA 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_COMMITA stored record was re-sent but its chain of custody could not be established, so it was refused.
SERVER_ERROROur side failed. Retried automatically.
INVALID_ENVELOPE, INVALID_SNAPSHOTDefined 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.