Documentation Index Fetch the complete documentation index at: https://docs.spitshake.io/llms.txt
Use this file to discover all available pages before exploring further.
JWT Tokens
DocuTrust uses JSON Web Tokens (JWT) to authenticate embedded signing forms and template builders. Tokens are generated server-side, scoped to a specific resource (submitter or template), and passed to the frontend embed components.
Overview
Property Value Algorithm HS256 (HMAC-SHA256) Default expiration 1 hour (general), 4 hours (form), 4 hours (builder) Signing key Account JWT secret (falls back to SECRET_KEY_BASE if not set) Token endpoint POST /api/embed/token
Token types
DocuTrust issues two types of embed tokens, each scoped by the aud (audience) claim.
Form tokens authenticate a signing session for a specific submitter. The sub claim contains the submitter ID, and the sid claim contains the submission ID.
Full JWT claims:
{
"sub" : "142" ,
"iss" : "docutrust" ,
"aud" : "form" ,
"sid" : "89" ,
"aid" : "1" ,
"iat" : 1744195920 ,
"exp" : 1744210320 ,
"opts" : {
"allow_to_resubmit" : false ,
"with_title" : true ,
"with_send_copy_button" : true ,
"with_download_button" : true
}
}
Claim reference:
Claim Type Description substring Submitter ID. Identifies which signer this token is for. issstring Issuer. Always "docutrust". audstring Audience. "form" for signing form tokens. sidstring Submission ID. The parent submission this submitter belongs to. aidstring Account ID. The DocuTrust account that owns this submission. iatinteger Issued at. Unix timestamp when the token was generated. expinteger Expiration. Unix timestamp when the token expires. Default: 4 hours after iat. opts.allow_to_resubmitboolean Whether the signer can resubmit after completing. opts.with_titleboolean Whether to show the template title in the form. opts.with_send_copy_buttonboolean Whether to show the “Send a copy” button. opts.with_download_buttonboolean Whether to show the download button on the completion screen.
Builder tokens
Builder tokens authenticate access to the template editor for a specific template. The sub claim contains the template ID.
Full JWT claims:
{
"sub" : "15" ,
"iss" : "docutrust" ,
"aud" : "builder" ,
"aid" : "1" ,
"iat" : 1744195920 ,
"exp" : 1744210320 ,
"opts" : {
"autosave" : true ,
"preview" : true ,
"fields" : []
}
}
Claim reference:
Claim Type Description substring Template ID. Identifies which template the builder will load. issstring Issuer. Always "docutrust". audstring Audience. "builder" for template builder tokens. aidstring Account ID. The DocuTrust account that owns this template. iatinteger Issued at. Unix timestamp when the token was generated. expinteger Expiration. Unix timestamp when the token expires. Default: 4 hours after iat. opts.autosaveboolean Whether the builder auto-saves changes. opts.previewboolean Whether to show the document preview panel. opts.fieldsarray Pre-defined field definitions. Empty array means all field types are available.
When opts.fields is populated, it contains field definition objects:
{
"sub" : "15" ,
"iss" : "docutrust" ,
"aud" : "builder" ,
"aid" : "1" ,
"iat" : 1744195920 ,
"exp" : 1744210320 ,
"opts" : {
"autosave" : true ,
"preview" : true ,
"fields" : [
{ "name" : "Full Name" , "type" : "text" , "role" : "Recipient" , "required" : true },
{ "name" : "Signature" , "type" : "signature" , "role" : "Recipient" , "required" : true },
{ "name" : "Date Signed" , "type" : "date" , "role" : "Recipient" , "required" : true },
{ "name" : "Agreed to Terms" , "type" : "checkbox" , "role" : "Recipient" , "required" : true }
]
}
}
Expiration defaults
Constant Duration Description DEFAULT_EXPIRATION1 hour General-purpose token lifetime FORM_EXPIRATION4 hours Signing form token lifetime BUILDER_EXPIRATION4 hours Template builder token lifetime
Tokens are validated on every request. An expired token returns a 401 Unauthorized response with:
{
"error" : "token_expired" ,
"message" : "The embed token has expired. Generate a new token via POST /api/embed/token." ,
"expired_at" : "2026-04-08T18:12:00.000Z"
}
Generating tokens via the API
Use POST /api/embed/token to generate embed tokens. Authenticate with your API token in the X-Auth-Token header.
curl -X POST https://your-app.com/api/embed/token \
-H "X-Auth-Token: YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"audience": "form",
"submitter_id": 142,
"submission_id": 89,
"options": {
"allow_to_resubmit": false,
"with_title": true,
"with_send_copy_button": true,
"with_download_button": true
}
}'
Response:
{
"token" : "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxNDIiLCJpc3MiOiJkb2N1dHJ1c3QiLCJhdWQiOiJmb3JtIiwic2lkIjoiODkiLCJhaWQiOiIxIiwiaWF0IjoxNzQ0MTk1OTIwLCJleHAiOjE3NDQyMTAzMjAsIm9wdHMiOnsiYWxsb3dfdG9fcmVzdWJtaXQiOmZhbHNlLCJ3aXRoX3RpdGxlIjp0cnVlLCJ3aXRoX3NlbmRfY29weV9idXR0b24iOnRydWUsIndpdGhfZG93bmxvYWRfYnV0dG9uIjp0cnVlfX0.Xq2nJ8vGhW4kP9mR" ,
"expires_at" : "2026-04-08T18:12:00.000Z" ,
"audience" : "form" ,
"submitter_id" : 142 ,
"submission_id" : 89
}
Generate a builder token
curl -X POST https://your-app.com/api/embed/token \
-H "X-Auth-Token: YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"audience": "builder",
"template_id": 15,
"options": {
"autosave": true,
"preview": true,
"fields": [
{"name": "Full Name", "type": "text", "role": "Recipient", "required": true},
{"name": "Signature", "type": "signature", "role": "Recipient", "required": true},
{"name": "Date Signed", "type": "date", "role": "Recipient", "required": true},
{"name": "Agreed to Terms", "type": "checkbox", "role": "Recipient", "required": true}
]
}
}'
Response:
{
"token" : "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxNSIsImlzcyI6ImRvY3V0cnVzdCIsImF1ZCI6ImJ1aWxkZXIiLCJhaWQiOiIxIiwiaWF0IjoxNzQ0MTk1OTIwLCJleHAiOjE3NDQyMTAzMjAsIm9wdHMiOnsiYXV0b3NhdmUiOnRydWUsInByZXZpZXciOnRydWUsImZpZWxkcyI6W3sibmFtZSI6IkZ1bGwgTmFtZSIsInR5cGUiOiJ0ZXh0Iiwicm9sZSI6IlJlY2lwaWVudCIsInJlcXVpcmVkIjp0cnVlfSx7Im5hbWUiOiJTaWduYXR1cmUiLCJ0eXBlIjoic2lnbmF0dXJlIiwicm9sZSI6IlJlY2lwaWVudCIsInJlcXVpcmVkIjp0cnVlfSx7Im5hbWUiOiJEYXRlIFNpZ25lZCIsInR5cGUiOiJkYXRlIiwicm9sZSI6IlJlY2lwaWVudCIsInJlcXVpcmVkIjp0cnVlfSx7Im5hbWUiOiJBZ3JlZWQgdG8gVGVybXMiLCJ0eXBlIjoiY2hlY2tib3giLCJyb2xlIjoiUmVjaXBpZW50IiwicmVxdWlyZWQiOnRydWV9XX19.abc123" ,
"expires_at" : "2026-04-08T18:12:00.000Z" ,
"audience" : "builder" ,
"template_id" : 15
}
Server-side token generation
For tighter control or when you want to generate tokens without calling the API, you can sign JWTs directly using your account’s JWT secret.
Ruby
require 'jwt'
# Your account's JWT secret (or falls back to SECRET_KEY_BASE)
jwt_secret = ENV . fetch ( 'DOCUTRUST_JWT_SECRET' ) { ENV . fetch ( 'SECRET_KEY_BASE' ) }
# Generate a form token
form_payload = {
sub: '142' ,
iss: 'docutrust' ,
aud: 'form' ,
sid: '89' ,
aid: '1' ,
iat: Time . now . to_i ,
exp: Time . now . to_i + ( 4 * 3600 ), # 4 hours
opts: {
allow_to_resubmit: false ,
with_title: true ,
with_send_copy_button: true ,
with_download_button: true
}
}
form_token = JWT . encode (form_payload, jwt_secret, 'HS256' )
puts "Form token: #{ form_token } "
# Generate a builder token
builder_payload = {
sub: '15' ,
iss: 'docutrust' ,
aud: 'builder' ,
aid: '1' ,
iat: Time . now . to_i ,
exp: Time . now . to_i + ( 4 * 3600 ), # 4 hours
opts: {
autosave: true ,
preview: true ,
fields: [
{ name: 'Full Name' , type: 'text' , role: 'Recipient' , required: true },
{ name: 'Signature' , type: 'signature' , role: 'Recipient' , required: true },
{ name: 'Date Signed' , type: 'date' , role: 'Recipient' , required: true },
{ name: 'Agreed to Terms' , type: 'checkbox' , role: 'Recipient' , required: true }
]
}
}
builder_token = JWT . encode (builder_payload, jwt_secret, 'HS256' )
puts "Builder token: #{ builder_token } "
Node.js
const jwt = require ( 'jsonwebtoken' );
// Your account's JWT secret (or falls back to SECRET_KEY_BASE)
const jwtSecret = process . env . DOCUTRUST_JWT_SECRET || process . env . SECRET_KEY_BASE ;
// Generate a form token
const formPayload = {
sub: '142' ,
iss: 'docutrust' ,
aud: 'form' ,
sid: '89' ,
aid: '1' ,
iat: Math . floor ( Date . now () / 1000 ),
exp: Math . floor ( Date . now () / 1000 ) + ( 4 * 3600 ), // 4 hours
opts: {
allow_to_resubmit: false ,
with_title: true ,
with_send_copy_button: true ,
with_download_button: true
}
};
const formToken = jwt . sign ( formPayload , jwtSecret , { algorithm: 'HS256' });
console . log ( 'Form token:' , formToken );
// Generate a builder token
const builderPayload = {
sub: '15' ,
iss: 'docutrust' ,
aud: 'builder' ,
aid: '1' ,
iat: Math . floor ( Date . now () / 1000 ),
exp: Math . floor ( Date . now () / 1000 ) + ( 4 * 3600 ), // 4 hours
opts: {
autosave: true ,
preview: true ,
fields: [
{ name: 'Full Name' , type: 'text' , role: 'Recipient' , required: true },
{ name: 'Signature' , type: 'signature' , role: 'Recipient' , required: true },
{ name: 'Date Signed' , type: 'date' , role: 'Recipient' , required: true },
{ name: 'Agreed to Terms' , type: 'checkbox' , role: 'Recipient' , required: true }
]
}
};
const builderToken = jwt . sign ( builderPayload , jwtSecret , { algorithm: 'HS256' });
console . log ( 'Builder token:' , builderToken );
Python
import jwt
import time
import os
# Your account's JWT secret (or falls back to SECRET_KEY_BASE)
jwt_secret = os.environ.get( 'DOCUTRUST_JWT_SECRET' ) or os.environ[ 'SECRET_KEY_BASE' ]
now = int (time.time())
# Generate a form token
form_payload = {
'sub' : '142' ,
'iss' : 'docutrust' ,
'aud' : 'form' ,
'sid' : '89' ,
'aid' : '1' ,
'iat' : now,
'exp' : now + ( 4 * 3600 ), # 4 hours
'opts' : {
'allow_to_resubmit' : False ,
'with_title' : True ,
'with_send_copy_button' : True ,
'with_download_button' : True
}
}
form_token = jwt.encode(form_payload, jwt_secret, algorithm = 'HS256' )
print ( f 'Form token: { form_token } ' )
# Generate a builder token
builder_payload = {
'sub' : '15' ,
'iss' : 'docutrust' ,
'aud' : 'builder' ,
'aid' : '1' ,
'iat' : now,
'exp' : now + ( 4 * 3600 ), # 4 hours
'opts' : {
'autosave' : True ,
'preview' : True ,
'fields' : [
{ 'name' : 'Full Name' , 'type' : 'text' , 'role' : 'Recipient' , 'required' : True },
{ 'name' : 'Signature' , 'type' : 'signature' , 'role' : 'Recipient' , 'required' : True },
{ 'name' : 'Date Signed' , 'type' : 'date' , 'role' : 'Recipient' , 'required' : True },
{ 'name' : 'Agreed to Terms' , 'type' : 'checkbox' , 'role' : 'Recipient' , 'required' : True }
]
}
}
builder_token = jwt.encode(builder_payload, jwt_secret, algorithm = 'HS256' )
print ( f 'Builder token: { builder_token } ' )
Security best practices
Generate tokens server-side only
Never expose your JWT secret to the browser. Tokens must always be generated on your backend and passed to the frontend. The JWT secret should only exist in environment variables on your server. // WRONG: Never do this in browser code
const token = jwt . sign ( payload , 'my-secret' );
// RIGHT: Fetch a token from your backend
const response = await fetch ( '/api/internal/embed-token' , { method: 'POST' });
const { token } = await response . json ();
Use short expiration times
The default 4-hour expiration is suitable for most use cases. For high-security environments, generate tokens with shorter lifetimes (15-60 minutes) and refresh them as needed. # High-security: 15-minute token
payload = {
sub: submitter_id. to_s ,
iss: 'docutrust' ,
aud: 'form' ,
sid: submission_id. to_s ,
aid: account_id. to_s ,
iat: Time . now . to_i ,
exp: Time . now . to_i + ( 15 * 60 ), # 15 minutes
opts: {
allow_to_resubmit: false ,
with_title: true ,
with_send_copy_button: true ,
with_download_button: true
}
}
Scope tokens to specific resources
Each token should be scoped to exactly one submitter (form) or one template (builder). Never create tokens that grant access to multiple resources. The sub claim enforces this scoping.
If your JWT secret is compromised, rotate it immediately in your DocuTrust account settings. All existing tokens signed with the old secret will be invalidated. DocuTrust supports a previous key for graceful rotation — tokens signed with the previous key remain valid during the rotation window.
Validate the audience claim
When verifying tokens on your backend (e.g., in webhook handlers), always validate the aud claim matches the expected audience. A form token should never be accepted where a builder token is expected, and vice versa. decoded = JWT . decode (token, jwt_secret, true , {
algorithm: 'HS256' ,
iss: 'docutrust' ,
verify_iss: true ,
aud: 'form' ,
verify_aud: true
})
Do not embed tokens in URLs
Avoid passing JWT tokens as URL query parameters. URLs are logged in server access logs, browser history, and proxy logs. Instead, pass tokens via data-token attributes or JavaScript configuration objects. <!-- WRONG: Token in URL (logged everywhere) -->
< iframe src = "https://your-app.com/embed?token=eyJ..." ></ iframe >
<!-- RIGHT: Token in data attribute -->
< docuseal-builder data-token = "eyJ..." ></ docuseal-builder >
Token flow diagram
The typical token flow for embedding:
Backend generates token
Your server calls POST /api/embed/token (or signs a JWT directly) with the submitter/template ID and desired options.
Token sent to frontend
Your backend returns the token to the browser, either as part of an HTML page render or via an AJAX response.
Frontend passes token to embed
The token is set as a data-token attribute on the web component, or passed to DocuTrust.mount() / DocuTrust.mountBuilder().
Embed validates token
The DocuTrust iframe receives the token, validates the signature and expiration, and loads the appropriate resource (form or builder).
Token expires
After the configured expiration time, the token is no longer valid. If the user’s session is still active, your backend should generate a fresh token.
Troubleshooting
Error Cause Fix 401 token_expiredToken exp claim is in the past Generate a new token with a fresh exp 401 invalid_signatureToken signed with wrong secret Verify you are using the correct JWT secret for this account 401 invalid_audienceToken aud does not match endpoint Use aud: "form" for signing forms and aud: "builder" for the builder 401 invalid_issuerToken iss is not "docutrust" Set iss: "docutrust" in the token payload 403 account_mismatchToken aid does not match the resource’s account Ensure the submitter/template belongs to the account in the token 404 submitter_not_foundToken sub references a non-existent submitter Verify the submitter ID exists and has not been archived 404 template_not_foundToken sub references a non-existent template Verify the template ID exists and has not been archived