Overview
Traditional webhook authentication using shared secrets creates critical vulnerabilities: secrets must be distributed and stored securely, rotation requires coordinating both parties, and any compromise affects all integrations. JWT signatures eliminate these risks through asymmetric cryptography - you verify requests using our public JWKS endpoint without storing any secrets, while we can rotate keys unilaterally without disrupting your service. This zero-trust approach provides cryptographic proof of authenticity, integrity, and origin for every request, with built-in replay prevention through unique token identifiers.Security Guarantees
Each JWT token provides cryptographic proof of:- Authenticity: Request originated from Bland’s infrastructure (verified via signature)
- Integrity: Payload hasn’t been modified (signature breaks if tampered)
- Non-repudiation: Bland cannot deny sending the request
- Replay prevention: Unique
jti
(UUIDv7) prevents token reuse - Time-boxing:
iat
,nbf
, andexp
claims enforce temporal validity
Token Structure
Every JWT Bland issues contains these claims and headers:Header | Type | Purpose | Validation Required |
---|---|---|---|
alg | String | Signing algorithm (RS256) | Must be RS256 |
typ | String | Token type (JWT) | Must be JWT |
kid | String | Key ID for key pair lookup | Must match a key in your JWKS |
Claim | Type | Purpose | Validation Required |
---|---|---|---|
sub | String | Org ID | Must match your application’s Bland Org ID |
jti | UUIDv7 | Unique token identifier | Track to prevent replay attacks |
iat | Unix timestamp | Issued at time | Reject if future (clock skew) |
nbf | Unix timestamp | Not valid before | Reject if current time < nbf |
exp | Unix timestamp | Expiration time | Reject if expired |
iss | String | Issuer URL | Must match https://api.bland.ai/orgs/{org_id} |
aud | String | Intended recipient | Must match your service identifier |
Configuration

Core Settings
JWT Signing: Master toggle for signature generation Audience (aud):- Leave blank: Auto-populates with request’s base URL in the form of protocol://host
- Custom value: Use for service mesh routing (e.g.,
svc:webhook-processor
) - Examples (when auto populated):
https://webhooks.yourdomain.com?queryparam=value
=>https://webhooks.yourdomain.com
https://api.yourdomain.com/v1/endpoint
=>https://api.yourdomain.com
- Use the “Include Protocol” option to control whether the
https://
prefix is included in theaud
claim.
- Shorter = Better security, more clock skew issues
- Longer = More tolerant, larger replay window
Key Management
JWKS Endpoint
Public keys available at:Rotation Strategy
Standard Rotation:- New key generated with unique
kid
- 24-hour overlap with previous keys (max 3 active)
- In-flight requests remain valid during transition
- Your cached JWKS continues working
- Immediately invalidates ALL tokens
- Use only for confirmed compromise
- Will break in-flight requests
- Requires immediate cache purge on your side
Implementation
Verification Example
Cache Invalidation Strategy
When verification fails with an unknownkid
:
- Force refresh your JWKS cache immediately
- Retry verification once with fresh keys
- If still failing, reject the request
- Log the failure with the
kid
for investigation
Security Considerations
Why This Prevents Breaches
Compromised database: Attacker finds your database backup- Shared secret: Can forge webhooks immediately
- JWT: No secrets stored, cannot forge signatures
- Shared secret: Can replay requests or extract secret
- JWT: Can replay only until
exp
, cannot forge new requests
- Shared secret: Anyone with production access has the secret
- JWT: Would need Bland’s private key (never exposed)
- Shared secret: If secrets are reused, you’re affected
- JWT: Each org has unique issuer, no cross-contamination
Production Requirements
- Always verify
jti
uniqueness within the token’s lifetime - Cache JWKS for 1-24 hours, refresh on unknown
kid
- Implement replay prevention using Redis or similar
- Monitor verification failures - spike indicates rotation or attack
- Set clock tolerance to ±30 seconds for distributed systems
- Never accept tokens with future
iat
(issued-at) times
Troubleshooting
ERR_JWKS_NO_MATCHING_KEY errors
ERR_JWKS_NO_MATCHING_KEY errors
Cause: Key rotation occurred, your cache has old keysFix: Force JWKS cache refresh and retry once
Token expired errors during normal operations
Token expired errors during normal operations
Cause: Clock skew or network latency exceeding token lifetime Fix:
Increase token expiration to 600-900 seconds
Replay detection triggering on legitimate requests
Replay detection triggering on legitimate requests
Cause: Retry logic or load balancer sending duplicate requests Fix:
Implement idempotency keys separate from JWT
jti
Cannot verify after emergency revocation
Cannot verify after emergency revocation
Cause: All keys invalidated, including cached onesFix: Manually purge JWKS cache and fetch fresh keys