Integration Guides
Step-by-step tutorials for common verification workflows. From first API call to production-ready integration.
Getting Started
This guide walks you through creating your TrueCheck account, generating API keys, and sending your first SMS verification — all in under five minutes.
1. Create Your Account
Head to truecheck.co/signup and register with your email address. You'll receive a confirmation email — click the link to activate your account and access the dashboard.
2. Generate an API Key
Navigate to Dashboard → API Keys and click Create New Key. You'll get two keys:
- Live key (
tc_live_...) — sends real SMS messages and is billed - Test key (
tc_test_...) — sandbox mode, no SMS sent, no charges
3. Send Your First Verification
Make a POST request to the verification endpoint with your API key:
curl -X POST https://api.truecheck.co/v1/verify \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_KEY" \
-d '{
"phone_number": "+14155551234",
"channel": "sms"
}'4. Check the Verification Code
After the user receives the SMS and enters the code, verify it:
curl -X POST https://api.truecheck.co/v1/verify/check \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_KEY" \
-d '{
"verification_id": "ver_abc123",
"code": "483920"
}'A successful response returns "status": "approved". That's it — you've completed your first verification!
Phone Verification Flow
A production-ready phone verification flow needs more than just send-and-check. This guide covers the complete lifecycle including input validation, retry logic, expiration handling, and user experience best practices.
Flow Overview
The verification flow follows these steps:
- User enters their phone number
- Your server validates the number format and sends a verification request
- TrueCheck delivers an SMS with a 6-digit code
- User enters the code in your app
- Your server checks the code against TrueCheck's API
- Handle success or failure (retry / lockout)
Sending the Verification
const response = await fetch('https://api.truecheck.co/v1/verify', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.TRUECHECK_API_KEY}`,
},
body: JSON.stringify({
phone_number: phoneNumber, // E.164 format: +14155551234
channel: 'sms',
locale: 'en', // optional — sets SMS language
code_length: 6, // optional — 4, 6, or 8
}),
});
const { verification_id, status } = await response.json();
// Store verification_id for the check stepChecking the Code
const response = await fetch('https://api.truecheck.co/v1/verify/check', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.TRUECHECK_API_KEY}`,
},
body: JSON.stringify({
verification_id: verificationId,
code: userEnteredCode,
}),
});
const { status } = await response.json();
if (status === 'approved') {
// Verification successful — mark phone as verified
} else {
// Code was wrong — let the user retry (max 3 attempts)
}Retry Logic
Each verification allows up to 3 code check attempts. After 3 failed attempts, the verification expires and you must send a new one. Allow the user to request a new code after 60 seconds to prevent abuse.
Input Validation
Always validate phone numbers before sending to the API. Accepted format is E.164: a + followed by country code and number, no spaces or dashes. Example: +14155551234.
Webhook Integration
Webhooks allow you to receive real-time notifications when verification events occur, eliminating the need to poll the API for status changes.
Setting Up Your Endpoint
Configure your webhook URL in Dashboard → Integrations → Webhooks. Your endpoint must:
- Accept POST requests with JSON body
- Return a 2xx status code within 5 seconds
- Be publicly accessible via HTTPS
Webhook Payload
Every webhook event includes a standard payload structure:
{
"event": "verification.status_changed",
"timestamp": "2026-03-16T12:00:00Z",
"data": {
"verification_id": "ver_abc123",
"phone_number": "+14155551234",
"status": "delivered",
"channel": "sms",
"previous_status": "pending"
},
"webhook_id": "wh_xyz789"
}Event Types
| Event | Description |
|---|---|
| verification.created | A new verification was initiated |
| verification.delivered | SMS was delivered to the carrier |
| verification.approved | Code was entered correctly |
| verification.failed | Max attempts reached or expired |
| verification.expired | Verification timed out (10 minutes) |
Verifying Signatures
Every webhook includes an X-TrueCheck-Signature header containing an HMAC-SHA256 signature. Always verify this to ensure the request came from TrueCheck:
import crypto from 'crypto';
function verifyWebhook(payload, signature, secret) {
const expected = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}Retry Policy
If your endpoint returns a non-2xx response or times out, TrueCheck retries with exponential backoff: 5s → 30s → 2min → 15min → 1hr (up to 5 retries). After all retries fail, the event is logged in your dashboard under Webhook Logs.
Error Handling
Robust error handling ensures a smooth user experience even when things go wrong. This guide covers common API errors, retry strategies, and user-facing messages.
Error Response Format
All API errors follow a consistent JSON format:
{
"error": {
"code": "invalid_phone_number",
"message": "The phone number provided is not in valid E.164 format.",
"status": 400,
"doc_url": "https://truecheck.co/docs/errors#invalid_phone_number"
}
}Common Error Codes
| Code | Status | What to Do |
|---|---|---|
| invalid_phone_number | 400 | Ask user to re-enter phone in E.164 format |
| rate_limit_exceeded | 429 | Wait and retry after the Retry-After header value |
| verification_expired | 410 | Send a new verification code |
| max_attempts_reached | 429 | Code entry locked — send new verification |
| insufficient_balance | 402 | Top up your account balance |
| unauthorized | 401 | Check your API key |
| carrier_unreachable | 503 | Retry after 30s or try a different channel |
Implementing Error Handling
async function sendVerification(phoneNumber) {
try {
const res = await fetch('https://api.truecheck.co/v1/verify', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${API_KEY}`,
},
body: JSON.stringify({ phone_number: phoneNumber, channel: 'sms' }),
});
if (!res.ok) {
const { error } = await res.json();
switch (error.code) {
case 'invalid_phone_number':
return { success: false, message: 'Please enter a valid phone number.' };
case 'rate_limit_exceeded':
return { success: false, message: 'Too many requests. Please wait a moment.' };
case 'insufficient_balance':
// Alert your ops team
return { success: false, message: 'Service temporarily unavailable.' };
default:
return { success: false, message: 'Something went wrong. Please try again.' };
}
}
const data = await res.json();
return { success: true, verificationId: data.verification_id };
} catch (err) {
// Network error
return { success: false, message: 'Connection error. Please check your internet.' };
}
}Rate Limiting
TrueCheck enforces rate limits to ensure fair usage and platform stability. Understanding these limits helps you design applications that scale smoothly.
Default Limits
| Endpoint | Limit | Window |
|---|---|---|
| POST /v1/verify | 100 requests | Per minute |
| POST /v1/verify/check | 300 requests | Per minute |
| GET /v1/verify/* | 600 requests | Per minute |
| Per phone number | 5 verifications | Per hour |
Rate Limit Headers
Every API response includes rate limit headers:
X-RateLimit-Limit: 100 X-RateLimit-Remaining: 87 X-RateLimit-Reset: 1710590400 Retry-After: 12 # only present on 429 responses
Exponential Backoff
When you receive a 429 response, implement exponential backoff:
async function fetchWithBackoff(url, options, maxRetries = 3) {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
const res = await fetch(url, options);
if (res.status !== 429) return res;
const retryAfter = res.headers.get('Retry-After');
const delay = retryAfter
? parseInt(retryAfter) * 1000
: Math.min(1000 * Math.pow(2, attempt), 30000);
await new Promise(r => setTimeout(r, delay));
}
throw new Error('Max retries exceeded');
}Testing in Sandbox
Sandbox mode lets you develop and test your integration without sending real SMS messages or incurring charges. Use your test API key (tc_test_...) to activate sandbox mode automatically.
Magic Phone Numbers
In sandbox mode, these phone numbers trigger specific behaviors:
| Phone Number | Behavior |
|---|---|
| +15550000001 | Always succeeds — code is always 123456 |
| +15550000002 | Always fails — simulates delivery failure |
| +15550000003 | Delayed delivery — delivered after 10 seconds |
| +15550000004 | Carrier error — simulates carrier rejection |
| +15550000005 | Expired — verification expires immediately |
Predictable Codes
For any non-magic phone number in sandbox, the verification code is always 123456. This makes it easy to write automated tests:
// In your test suite
const SANDBOX_CODE = '123456';
test('verification flow completes successfully', async () => {
// Send verification
const sendRes = await api.sendVerification('+14155551234');
expect(sendRes.status).toBe('pending');
// Check with the predictable code
const checkRes = await api.checkCode(sendRes.verification_id, SANDBOX_CODE);
expect(checkRes.status).toBe('approved');
});Sandbox vs Live Differences
- No real SMS is sent — instant "delivery"
- No billing — all sandbox requests are free
- Webhooks still fire (so you can test your webhook handler)
- Rate limits are the same as production
- Response format is identical to live mode
TRUECHECK_API_KEY to your test key in your CI environment variables so automated tests never send real SMS.Can't find what you need?
Our engineering team is happy to help with custom integration patterns or advanced use cases.
