CleverTap ZeroPii SDK provides a secure way to tokenize Personally Identifiable Information (PII) in your web applications. By replacing sensitive data with format-preserving tokens, you can minimize the exposure of sensitive information while maintaining data utility. This SDK provides a Promise-based TypeScript API for seamless integration.
- Type-Safe Tokenization: Support for String, Int, Long, Float, Double, Boolean
- Batch Operations: Process up to 1,000 values in a single request
- Token Caching: Automatic OAuth token caching with 30-second expiry buffer
- AES-256-GCM Encryption: End-to-end encryption with automatic 419 fallback
- Pluggable Retry Policy: Customize retry behavior or use built-in exponential backoff
- TypeScript Support: Full type safety and IntelliSense support
- Zero Dependencies: Uses native browser APIs (Web Crypto, fetch)
- Node.js: 16.x or higher (for development)
- npm: 8.x or higher
- TypeScript: 5.0 or higher (optional, for TypeScript projects)
- Chrome: 80+
- Firefox: 75+
- Safari: 14+
- Edge: 80+
The SDK requires browsers with support for:
- Web Crypto API (for AES-256-GCM encryption)
- Fetch API
- ES2020+ features (Promises, async/await)
No runtime dependencies. The SDK uses native browser APIs exclusively.
npm install clevertap-web-zeropii-sdkThe SDK requires you to implement an AccessTokenProvider to fetch OAuth tokens:
import { AccessTokenProvider, AccessTokenInfo } from 'clevertap-web-zeropii-sdk';
class MyTokenProvider implements AccessTokenProvider {
async fetchToken(): Promise<AccessTokenInfo> {
// Fetch token from your OAuth server
const response = await fetch('https://auth.example.com/oauth/token', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'client_credentials',
client_id: 'your-client-id',
client_secret: 'your-client-secret'
})
});
const data = await response.json();
return {
token: data.access_token,
expiresInSeconds: data.expires_in // Required for automatic refresh
};
}
}import { ZeroPiiSDK, LogLevel } from 'clevertap-web-zeropii-sdk';
const sdk = ZeroPiiSDK.initialize({
tokenProvider: new MyTokenProvider(),
apiUrl: 'https://zeropii-api.clevertap.com/',
logLevel: LogLevel.INFO, // Optional, default: OFF
retryPolicy: myCustomRetryPolicy // Optional, default: built-in exponential backoff
});// Single value tokenization
const result = await sdk.tokenizeString('john.doe@example.com');
if (result.type === 'success') {
console.log('Token:', result.token);
console.log('Exists:', result.exists);
console.log('Newly Created:', result.newlyCreated);
} else {
console.error('Error:', result.message);
}
// Batch tokenization
const batchResult = await sdk.batchTokenizeStringValues([
'user1@example.com',
'user2@example.com',
'user3@example.com'
]);
if (batchResult.type === 'success') {
console.log('Processed:', batchResult.summary.processedCount);
console.log('Results:', batchResult.results);
}Initialize the SDK singleton. Must be called before any operations.
interface ZeroPiiConfig {
tokenProvider: AccessTokenProvider; // Required: OAuth token provider
apiUrl: string; // Required: Vault API base URL
logLevel?: LogLevel; // Optional: OFF | ERROR | INFO | DEBUG | VERBOSE
retryPolicy?: RetryPolicy; // Optional: Custom retry policy
}Get the singleton instance (must call initialize() first).
const sdk = ZeroPiiSDK.getInstance();All methods return Promise<TokenizeResult>:
type TokenizeResult =
| {
type: 'success';
token: string;
exists: boolean; // Token already existed
newlyCreated: boolean; // Token created in this request
dataType: string | null;
}
| {
type: 'error';
message: string;
code?: ErrorCode;
statusCode?: number; // HTTP status code (undefined for network errors)
};// String
await sdk.tokenizeString(value: string): Promise<TokenizeResult>
// Integer
await sdk.tokenizeInt(value: number): Promise<TokenizeResult>
// Long (64-bit integer)
await sdk.tokenizeLong(value: number): Promise<TokenizeResult>
// Float
await sdk.tokenizeFloat(value: number): Promise<TokenizeResult>
// Double
await sdk.tokenizeDouble(value: number): Promise<TokenizeResult>
// Boolean
await sdk.tokenizeBool(value: boolean): Promise<TokenizeResult>await sdk.tokenize(value: string | number | boolean): Promise<TokenizeResult>Example:
// Dynamic form data
const formData: Record<string, string | number | boolean> = getFormData();
for (const [key, value] of Object.entries(formData)) {
const result = await sdk.tokenize(value);
// Handle result
}Process up to 1,000 values in a single request. Returns Promise<BatchTokenizeResult>:
type BatchTokenizeResult =
| {
type: 'success';
results: BatchTokenizeItemResult[];
summary: {
processedCount: number;
existingCount: number;
newlyCreatedCount: number;
};
}
| {
type: 'error';
message: string;
statusCode?: number; // HTTP status code (undefined for network errors)
};
interface BatchTokenizeItemResult {
originalValue: string;
token: string;
exists: boolean;
newlyCreated: boolean;
dataType: string | null;
}await sdk.batchTokenizeStringValues(values: string[]): Promise<BatchTokenizeResult>
await sdk.batchTokenizeIntValues(values: number[]): Promise<BatchTokenizeResult>
await sdk.batchTokenizeLongValues(values: number[]): Promise<BatchTokenizeResult>
await sdk.batchTokenizeFloatValues(values: number[]): Promise<BatchTokenizeResult>
await sdk.batchTokenizeDoubleValues(values: number[]): Promise<BatchTokenizeResult>
await sdk.batchTokenizeBoolValues(values: boolean[]): Promise<BatchTokenizeResult>Example:
const emails = ['user1@example.com', 'user2@example.com', 'user3@example.com'];
const result = await sdk.batchTokenizeStringValues(emails);
if (result.type === 'success') {
console.log(`Processed: ${result.summary.processedCount}`);
console.log(`Existing: ${result.summary.existingCount}`);
console.log(`New: ${result.summary.newlyCreatedCount}`);
result.results.forEach(item => {
console.log(`${item.originalValue} -> ${item.token}`);
});
}interface AccessTokenProvider {
fetchToken(): Promise<AccessTokenInfo>;
}
interface AccessTokenInfo {
token: string; // OAuth bearer token
expiresInSeconds: number; // Token lifetime (e.g., 300 for 5 minutes)
}Important: The expiresInSeconds field is required for automatic token caching and refresh.
- Tokens are cached automatically
- Tokens expire 30 seconds before actual expiration (safety buffer)
- Tokens are refreshed automatically when expired
- No manual token management needed
Example Flow:
- First call:
fetchToken()called, token cached - Subsequent calls (within expiry): Cached token used
- After expiry - 30s:
fetchToken()called again, new token cached
enum LogLevel {
OFF = 0, // No logging
ERROR = 1, // Errors only
INFO = 2, // Info + errors
DEBUG = 3, // Debug + info + errors
VERBOSE = 4 // All logs including network details
}interface RetryPolicy {
/**
* Determines whether the SDK should retry a failed request.
* @param attempt 0-based attempt index. 0 = after first failure (before first retry).
* @param httpStatusCode HTTP status code of failed response. undefined = network error.
* @return true to retry, false to stop and deliver the error.
*/
shouldRetry(attempt: number, httpStatusCode?: number): boolean;
/**
* Returns how long to wait before the next retry attempt.
* Only called when shouldRetry() returns true.
* @param attempt 0-based attempt index.
* @return delay in milliseconds. Return 0 for immediate retry.
*/
retryDelayMs(attempt: number): number;
}Implement this interface to customize retry behavior for your application.
Note: 401 Unauthorized responses bypass
RetryPolicyentirely — the SDK automatically refreshes the token and retries immediately (no delay).
If no custom retryPolicy is provided, the SDK uses a built-in exponential backoff policy:
- Retries on: Server errors (500, 502, 503, 504), rate limiting (429), and network errors (undefined status code)
- Does not retry on: Client errors (4xx except 401, which is handled internally)
- Exponential backoff delays: 2s, 4s, 8s, …
- Maximum retries: 1 by default
Here's an example of a linear retry policy that retries up to 3 times with a fixed 2-second delay:
import { RetryPolicy } from 'clevertap-web-zeropii-sdk';
class LinearRetryPolicy implements RetryPolicy {
private readonly maxRetries: number;
private readonly retryableStatusCodes = new Set([500, 502, 503, 504, 429]);
constructor(maxRetries: number = 3) {
this.maxRetries = maxRetries;
}
shouldRetry(attempt: number, httpStatusCode?: number): boolean {
if (attempt >= this.maxRetries) {
return false;
}
// Retry on network errors (undefined) or server errors
return httpStatusCode === undefined || this.retryableStatusCodes.has(httpStatusCode);
}
retryDelayMs(attempt: number): number {
return 2000; // Fixed 2-second delay
}
}
// Use it during initialization
const sdk = ZeroPiiSDK.initialize({
tokenProvider: new MyTokenProvider(),
apiUrl: 'https://zeropii-api.clevertap.com/',
retryPolicy: new LinearRetryPolicy(3)
});To disable retries entirely:
class NoRetryPolicy implements RetryPolicy {
shouldRetry(attempt: number, httpStatusCode?: number): boolean {
return false; // Never retry
}
retryDelayMs(attempt: number): number {
return 0; // Not used since shouldRetry always returns false
}
}Encryption is always enabled and uses the following:
- Algorithm: AES-256-GCM
- IV: 12-byte random nonce per request
- Tag: 128-bit authentication tag
- 419 Fallback: Automatically disables encryption if server doesn't support it
API request and response payloads are encrypted in transit for enhanced security.
Security Note: Session keys are transmitted over HTTPS. Always use HTTPS for your API endpoint to ensure secure key exchange. The SDK enforces encryption of the payload data, but relies on HTTPS for transport-layer security of the session key.
All operations use discriminated union results for type-safe error handling:
const result = await sdk.tokenizeString('value');
// Type guard
if (result.type === 'success') {
// TypeScript knows result has: token, exists, newlyCreated, dataType
console.log(result.token);
} else {
// TypeScript knows result has: message, code?, statusCode?
console.error(result.message);
// statusCode is present for API errors, undefined for network/SDK errors
if (result.statusCode) {
console.error(`HTTP ${result.statusCode}`);
}
}enum ErrorCode {
VALIDATION_EMPTY_VALUE = 'VALIDATION_EMPTY_VALUE',
VALIDATION_UNSUPPORTED_TYPE = 'VALIDATION_UNSUPPORTED_TYPE',
AUTH_TOKEN_FETCH_FAILED = 'AUTH_TOKEN_FETCH_FAILED',
AUTH_INVALID_CREDENTIALS = 'AUTH_INVALID_CREDENTIALS',
NETWORK_ERROR = 'NETWORK_ERROR',
NETWORK_TIMEOUT = 'NETWORK_TIMEOUT',
API_TOKENIZE_FAILED = 'API_TOKENIZE_FAILED',
API_ENCRYPTION_NOT_SUPPORTED = 'API_ENCRYPTION_NOT_SUPPORTED',
ENCRYPTION_FAILED = 'ENCRYPTION_FAILED',
DECRYPTION_FAILED = 'DECRYPTION_FAILED',
TYPE_CONVERSION_FAILED = 'TYPE_CONVERSION_FAILED',
UNKNOWN_ERROR = 'UNKNOWN_ERROR'
}npm install
npm run buildOutputs:
dist/esm/- ES modulesdist/cjs/- CommonJSdist/umd/- UMD (browser)
npm run demoOpens demo at http://localhost:3000
The demo application is included in the demo/ directory. To run it:
npm run demoConfigure your OAuth credentials in the demo UI or create a token provider:
// Create token provider for OAuth2 client credentials flow
class DemoTokenProvider implements AccessTokenProvider {
async fetchToken(): Promise<AccessTokenInfo> {
const response = await fetch(
'YOUR_AUTH_URL/protocol/openid-connect/token',
{
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'client_credentials',
client_id: 'YOUR_CLIENT_ID',
client_secret: 'YOUR_CLIENT_SECRET'
})
}
);
const data = await response.json();
return {
token: data.access_token,
expiresInSeconds: data.expires_in
};
}
}
const sdk = ZeroPiiSDK.initialize({
tokenProvider: new DemoTokenProvider(),
apiUrl: 'YOUR_API_URL',
logLevel: LogLevel.DEBUG
});- Singleton Pattern: One SDK instance per application
- Repository Pattern: Separation of data access (AuthRepository, TokenRepository)
- Strategy Pattern: Encryption with automatic 419 fallback
- Factory Pattern: Type converters and encryption strategies
- Provider Pattern: Dependency injection for token provider
src/
├── core/
│ └── ZeroPiiSDK.ts # Main SDK class
├── repository/
│ ├── AuthRepository.ts # Token caching with 30s expiry buffer
│ └── TokenRepository.ts # Tokenization logic
├── network/
│ ├── NetworkProvider.ts # HTTP client
│ ├── TokenizationApi.ts # API endpoints
│ └── RetryHandler.ts # Retry logic
├── encryption/
│ ├── EncryptionManager.ts # Encryption orchestration
│ ├── NetworkEncryptionManager.ts # AES-GCM implementation
│ ├── EncryptionStrategy.ts # Strategy pattern
│ └── AESGCMCrypt.ts # Web Crypto API wrapper
├── types/
│ ├── config.ts # Configuration types
│ ├── models.ts # API models
│ ├── results.ts # Result types
│ └── errors.ts # Error types
└── utils/
├── Logger.ts # Logging utility
└── TypeConverter.ts # Type conversion registry
Full TypeScript support with complete type definitions:
import {
ZeroPiiSDK,
ZeroPiiConfig,
AccessTokenProvider,
AccessTokenInfo,
TokenizeResult,
BatchTokenizeResult,
RetryPolicy,
LogLevel,
ErrorCode
} from 'clevertap-web-zeropii-sdk';reset()method removed - Singleton cannot be resetretryConfigremoved - Retry settings now hardcoded (maxRetries=1)- OAuth credentials removed - Use
AccessTokenProviderpattern instead - Cache methods removed - Token caching now automatic (access tokens only)
expiresInSecondsrequired - Must be provided inAccessTokenInfo
Before (v1.x):
ZeroPiiSDK.initialize({
clientId: 'YOUR_CLIENT_ID',
clientSecret: 'YOUR_CLIENT_SECRET',
apiUrl: 'https://api.example.com/',
authUrl: 'https://auth.example.com/',
retryConfig: { maxRetries: 3 },
cacheConfig: { enabled: true }
});
ZeroPiiSDK.reset(); // Available
sdk.clearCache(); // AvailableAfter (v2.0):
class MyTokenProvider implements AccessTokenProvider {
async fetchToken(): Promise<AccessTokenInfo> {
// Fetch from your OAuth server
return {
token: 'access_token',
expiresInSeconds: 300 // Required
};
}
}
ZeroPiiSDK.initialize({
tokenProvider: new MyTokenProvider(),
apiUrl: 'https://api.example.com/'
// No authUrl, retryConfig, or cacheConfig
});
// ZeroPiiSDK.reset() - Removed
// sdk.clearCache() - Removed (automatic caching)When deploying this SDK to production, follow these security guidelines:
- Required: Your API endpoint MUST use HTTPS
- Session keys are transmitted over the network and require transport-layer encryption
- Never use HTTP for production deployments
- Never hardcode OAuth credentials in your application code
- Use environment variables or secure credential management services (e.g., AWS Secrets Manager, Azure Key Vault)
- Implement proper access controls for credential storage
- Rotate credentials regularly
Implement the following HTTP security headers in your application:
Content-Security-Policy: default-src 'self'; script-src 'self'; connect-src 'self' https://your-api-domain.com
Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block- Recommended: Use
LogLevel.ERRORorLogLevel.OFFin production - Never use
LogLevel.DEBUGorLogLevel.VERBOSEin production as they may log sensitive data - Log data is automatically redacted, but limiting log verbosity adds an extra layer of security
const sdk = ZeroPiiSDK.initialize({
tokenProvider: new MyTokenProvider(),
apiUrl: 'https://api.example.com/',
logLevel: LogLevel.ERROR // Production recommendation
});- Implement proper error handling in your
AccessTokenProvider - Never log or expose OAuth tokens
- Validate token responses before returning them
- Consider implementing token refresh retry logic with backoff
If loading the SDK from a CDN, use Subresource Integrity hashes to ensure the file hasn't been tampered with:
<script src="https://cdn.example.com/zeropii-sdk@1.0.0/index.js"
integrity="sha384-[HASH]"
crossorigin="anonymous"></script>Generate SRI hashes using: https://www.srihash.org/ or openssl dgst -sha384 -binary <file> | openssl base64 -A
While the SDK performs input validation, implement additional validation in your application:
- Validate data before sending to tokenization
- Sanitize user inputs to prevent injection attacks
- Enforce business logic constraints (e.g., email format, phone number format)
- Implement client-side rate limiting to prevent accidental DoS
- Monitor API usage and set up alerts for unusual patterns
- Consider implementing request throttling
- Never expose detailed error messages to end users
- Log errors securely for debugging
- Implement user-friendly error messages that don't leak sensitive information
- Keep the SDK updated to the latest version
- Regularly run
npm auditto check for vulnerabilities - Subscribe to security advisories for dependencies
If you discover a security vulnerability, please report it responsibly:
- Email: security@clevertap.com
- Do not open public GitHub issues for security vulnerabilities
- Include detailed steps to reproduce the issue
MIT
Issues and pull requests welcome at github.com/clevertap/clevertap-web-zeropii-sdk
For questions or issues:
- Open an issue on GitHub
- Contact CleverTap support at support@clevertap.com