Skip to main content
Speed up ID Bolt integration with Agent Skills

Install the Scandit plugin and use the /id-bolt skill so that your AI coding agent can integrate, debug, and customize ID Bolt following Scandit's recommended patterns. More info →

Run the following command in your project directory. It detects the supported coding agents you have installed and adds the Scandit plugin to each. Re-run it to update.

npx plugins add scandit/skills

Prefer to set it up yourself? Manual installation steps for each agent →

Custom Domain Launch Token

Available since version 2.7.

When ID Bolt runs on your own custom domain (for example, scan.example.com), use a launch token to make sure that only your website can start sessions on this domain. Without a launch token, a malicious actor could forge sessions on your domain and send users to them.

With a launch token:

  1. Your server creates a launch token with a secret API key. The server sends the launch token and the session configuration, as a JSON string, to the page in a launch object. Create the launch token immediately before you open ID Bolt and, if possible, only for an authenticated user.
  2. Your page starts ID Bolt with this launch object.
  3. ID Bolt verifies the token on the desktop and on the smartphone. If the token is not valid, not for your domain, or the configuration was changed, the session does not start and ID Bolt shows an error page.

The configuration does not go to the Scandit servers. Your server sends only the SHA-256 digest of the configuration string, and ID Bolt compares it with the digest of the string that it receives in the browser. Thus the content of the configuration, for example text overrides that contain personal data, stays between your server, your page, and the devices of the user.

warning

On a custom domain, ID Bolt 2.7 and higher requires a launch token. Integrate launch tokens before you upgrade. Scandit can also make launch tokens necessary for older ID Bolt versions on your custom domain. Shared Scandit domains (for example, app.id-scanning.com) do not require a launch token.

Get an API Key​

Contact Scandit support to get a secret API key for your custom domain. Each API key is bound to one custom domain. You can have more than one key for the same domain, and one key for each additional domain.

caution

The API key is a secret. Use it only on your server. Do not send it to the browser.

Create a Launch Token on Your Server​

Create the launch token on your server with a REST call to your custom domain. If your backend uses Node.js, you can also use the Node.js server SDK.

REST API​

To create a launch token:

  1. Write the configuration as a JSON string, with any JSON library. See Launch Configuration.
  2. Calculate the SHA-256 digest of the UTF-8 bytes of this string, as lowercase hexadecimal.
  3. Send only the digest to POST https://<your-custom-domain>/api/v1/launch-tokens.
  4. Add the configuration string, unchanged, to the response as configuration. The result is the launch object. Send it to the page.

Example in Python:

import hashlib, json, secrets, requests

configuration = json.dumps({
"licenseKey": "<<YOUR LICENSE KEY>>",
"documentSelection": {"acceptedDocuments": [{"type": "Passport", "region": "Any"}]},
"returnDataMode": "Full",
"textOverrides": {"titles.SCANNER_HEADER": "Scan the passport of Jane Doe"},
"nonce": secrets.token_hex(16),
})
response = requests.post(
"https://scan.example.com/api/v1/launch-tokens",
headers={"Authorization": f"Bearer {ID_BOLT_API_KEY}"},
json={"configurationDigest": hashlib.sha256(configuration.encode("utf-8")).hexdigest()},
)
launch = response.json()
launch["configuration"] = configuration # the string itself: do not parse and serialize it again!

The request has these headers:

  • Authorization: Bearer <your API key>
  • Content-Type: application/json

The request body has one field:

  • configurationDigest (string, required): the SHA-256 digest of the configuration string, as 64 lowercase hexadecimal characters.
note

The validation is strict. An unknown field in the body gives a 400 response.

The response is:

{
"success": true,
"serviceUrl": "https://scan.example.com",
"launchToken": "...",
"sessionId": "t-...",
"expiresAt": "2026-10-22T10:30:00.000Z"
}

expiresAt is an ISO 8601 date. Keep the sessionId to match the scan result with your records.

The response does not contain the configuration. Before you send the launch object to the page, add your configuration string to it as configuration. This value is the string itself, not a JSON object.

If the request fails, the response is { "success": false, "error": "..." } with one of these status codes:

  • 400: The body is not valid JSON ("Invalid JSON body"), or the digest is not valid (configurationDigest: Invalid digest: expected the lowercase hex SHA-256 of the configuration string).
  • 401: The API key is missing or not valid ("Unauthorized").

Node.js Server SDK​

The @scandit/web-id-bolt package contains a server entry point, @scandit/web-id-bolt/server. Use the same package version as in the browser. Import it only in your backend (Node.js 20 or higher).

The SDK makes the nonce, writes the configuration string, calculates the digest, and sends it. It returns the launch object, with the launch token and the configuration string.

import { IdBoltServer } from "@scandit/web-id-bolt/server";
import { DocumentSelection, Passport, Region, ReturnDataMode } from "@scandit/web-id-bolt";

const idBoltServer = new IdBoltServer({
apiKey: ID_BOLT_API_KEY,
serviceUrl: "https://scan.example.com",
});

// Create one launch token for each page view that shows ID Bolt.
const launch = await idBoltServer.createLaunch({
configuration: {
licenseKey: LICENSE_KEY,
documentSelection: DocumentSelection.create({
accepted: [new Passport(Region.Any)],
}),
returnDataMode: ReturnDataMode.Full,
},
});

// Send `launch` to the page as it is. It is plain JSON:
// { serviceUrl, launchToken, sessionId, expiresAt, configuration }

Launch Configuration​

The configuration contains the same options as IdBoltSession.create, except:

  • The functions: validation, onCompletion, onCancellation.
  • The browser-only options: locale, keepAliveForNextSession, disableCloseOnBrowserBack.

You set these options on the page, in createFromLaunch.

If you use the REST API, write the configuration as JSON. It accepts these fields. Except for nonce, they have the same names and values as the options of IdBoltSession.create. ID Bolt fills in the default values of the fields that you do not set.

FieldJSON typeRequiredDetails
licenseKeystringYesYour Scandit license key
documentSelectionobjectYesacceptedDocuments is required. See Document below and Document Selection
noncestringYesAt least 16 random characters, new for each launch token. Without a nonce, a person who knows the other fields could find the text overrides from the digest
returnDataModestringNoData Handling
anonymizationModestringNoData Handling
anonymizedFieldsobjectNoData Handling
scannerobjectNoSee Scanner below and Workflow Options
workflowobjectNoWorkflow Options
themeobjectNoTheming
textOverridesobjectNoText Overrides
externalTransactionIdstringNoAdvanced Options
learnMoreobjectNoCustomize the "learn more" section

The service does not receive the configuration, so it cannot validate it when you create the launch token. ID Bolt validates it when the session starts. The validation is strict: an unknown field or a value that is not valid shows an error page that names the field. For example: Invalid launch configuration: documentSelection.acceptedDocuments.0.region: Invalid region: expected an ISO 3166-1 alpha-3 code or "Any".

tip

To find these errors before the session starts, validate your configuration on your server with the JSON Schema of the launch configuration: https://app.id-scanning.com/schemas/launch-configuration.schema.json. Use any JSON Schema validator, for example in your tests or before you calculate the digest.

Two shapes are different in JSON: Document and Scanner.

Document​

documentSelection contains these fields:

FieldJSON typeRequiredDetails
acceptedDocumentsDocument[]YesThe documents that you accept
rejectedDocumentsDocument[]NoThe documents that you reject

A document contains these fields:

FieldJSON typeRequiredDetails
typestringYesThe document type, for example "Passport" or "RegionSpecific"
regionstringYesAn ISO 3166-1 alpha-3 code, for example "DEU", or "Any"
subtypestringOnly for RegionSpecific documentsThe region-specific subtype, for example "usNexusCard". Other document types cannot have a subtype

Examples:

{ "type": "Passport", "region": "DEU" }
{ "type": "RegionSpecific", "region": "Any", "subtype": "usNexusCard" }

Scanner​

scanner contains these fields:

FieldJSON typeRequiredDetails
typestringYes"full" for the full document scanner, "singleSide" for the single side scanner
barcodebooleanOnly for "singleSide"Scan the barcode
machineReadableZonebooleanOnly for "singleSide"Scan the Machine Readable Zone (MRZ)
visualInspectionZonebooleanOnly for "singleSide"Scan the Visual Inspection Zone (VIZ)
enforceVizForPassportScanbooleanNo (only for "singleSide")See Passport VIZ Enforcement

Examples:

{ "type": "full" }
{ "type": "singleSide", "barcode": true, "machineReadableZone": true, "visualInspectionZone": true }

Configuration String​

ID Bolt calculates the digest of the exact bytes of the configuration string. Any spacing and any valid JSON escape is correct. But do not parse and serialize the string again after you calculate the digest: if a byte changes, the digest does not match and the session does not start.

To test your implementation, calculate the SHA-256 digest of this string:

{"licenseKey": "YOUR-LICENSE-KEY", "documentSelection": {"acceptedDocuments": [{"type": "Passport", "region": "Any"}]}, "textOverrides": {"titles.SCANNER_HEADER": "Scan the passport of Jos\u00e9"}, "nonce": "6f1c0e2a9b7d4c3e8a5f1b2d3c4e5f60"}

The result must be 24ba90f450f9b4f249104112514fbd0de76314ae6e2780e136573c1e619fac84. The string contains é as a JSON escape of 6 characters, as Python's json.dumps writes it. If you use the character é in its place, the bytes are different and the digest is different.

Start ID Bolt on the Page​

import { IdBoltSession } from "@scandit/web-id-bolt";

const idBoltSession = IdBoltSession.createFromLaunch(launch, {
onCompletion: (result) => {
// ...
},
// Also available: onCancellation, validation, locale,
// keepAliveForNextSession, disableCloseOnBrowserBack
});
await idBoltSession.start();

ID Bolt runs on launch.serviceUrl with launch.configuration. The page does not build the configuration again. launch.configuration must be a string: if it is not, createFromLaunch throws a TypeError. After the session starts, you cannot change the configuration.

Token Lifetime and Usage​

  • A launch token is valid for one session. It expires 30 minutes after you create it. The token must stay valid while the user is on the page and during the scan on the smartphone.
  • Create a new launch token for each session, immediately before you call start().
  • Create launch tokens only for authenticated users.
  • Do not change the launch token or the configuration string. If the configuration string is different from the string that you used to create the launch token, the session does not start. On the page, you can still complete the configuration with the options that are not in the configuration string: validation, onCompletion, onCancellation, locale, keepAliveForNextSession, and disableCloseOnBrowserBack. Give only the options that you need to IdBoltSession.createFromLaunch.

For example, on the page:

import { IdBoltSession, Validators } from "@scandit/web-id-bolt";

// Correct: give the launch object as it is, and add only the options that you need.
const idBoltSession = IdBoltSession.createFromLaunch(launch, {
locale: "de-DE",
validation: [Validators.notExpired()],
onCompletion: (result) => {
// ...
},
});
await idBoltSession.start();

// Not correct: the configuration string changes, so the session does not start.
const configuration = JSON.parse(launch.configuration);
configuration.returnDataMode = "Full";
launch.configuration = JSON.stringify(configuration);

The second example fails even if you do not change a value: JSON.stringify can write the string with different spacing or escapes, and then the digest does not match. To change the configuration, create a new launch token on your server.