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/skillsPrefer 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:
- 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
launchobject. Create the launch token immediately before you open ID Bolt and, if possible, only for an authenticated user. - Your page starts ID Bolt with this
launchobject. - 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.
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.
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:
- Write the configuration as a JSON string, with any JSON library. See Launch Configuration.
- Calculate the SHA-256 digest of the UTF-8 bytes of this string, as lowercase hexadecimal.
- Send only the digest to
POST https://<your-custom-domain>/api/v1/launch-tokens. - Add the configuration string, unchanged, to the response as
configuration. The result is thelaunchobject. 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.
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.
| Field | JSON type | Required | Details |
|---|---|---|---|
licenseKey | string | Yes | Your Scandit license key |
documentSelection | object | Yes | acceptedDocuments is required. See Document below and Document Selection |
nonce | string | Yes | At 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 |
returnDataMode | string | No | Data Handling |
anonymizationMode | string | No | Data Handling |
anonymizedFields | object | No | Data Handling |
scanner | object | No | See Scanner below and Workflow Options |
workflow | object | No | Workflow Options |
theme | object | No | Theming |
textOverrides | object | No | Text Overrides |
externalTransactionId | string | No | Advanced Options |
learnMore | object | No | Customize 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".
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:
| Field | JSON type | Required | Details |
|---|---|---|---|
acceptedDocuments | Document[] | Yes | The documents that you accept |
rejectedDocuments | Document[] | No | The documents that you reject |
A document contains these fields:
| Field | JSON type | Required | Details |
|---|---|---|---|
type | string | Yes | The document type, for example "Passport" or "RegionSpecific" |
region | string | Yes | An ISO 3166-1 alpha-3 code, for example "DEU", or "Any" |
subtype | string | Only for RegionSpecific documents | The 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:
| Field | JSON type | Required | Details |
|---|---|---|---|
type | string | Yes | "full" for the full document scanner, "singleSide" for the single side scanner |
barcode | boolean | Only for "singleSide" | Scan the barcode |
machineReadableZone | boolean | Only for "singleSide" | Scan the Machine Readable Zone (MRZ) |
visualInspectionZone | boolean | Only for "singleSide" | Scan the Visual Inspection Zone (VIZ) |
enforceVizForPassportScan | boolean | No (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, anddisableCloseOnBrowserBack. Give only the options that you need toIdBoltSession.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.