TrueCheck
Documentation

Integration Guides

Step-by-step tutorials for common verification workflows. From first API call to production-ready integration.

Guide 01

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
Tip: Start with your test key during development. Switch to the live key only when you're ready for production.

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!

Guide 02

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:

  1. User enters their phone number
  2. Your server validates the number format and sends a verification request
  3. TrueCheck delivers an SMS with a 6-digit code
  4. User enters the code in your app
  5. Your server checks the code against TrueCheck's API
  6. 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 step

Checking 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.

Best Practice: Show a countdown timer (e.g. "Resend code in 45s") after each send to reduce unnecessary retries. Verification codes expire after 10 minutes.

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.

Guide 03

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

EventDescription
verification.createdA new verification was initiated
verification.deliveredSMS was delivered to the carrier
verification.approvedCode was entered correctly
verification.failedMax attempts reached or expired
verification.expiredVerification 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.

Guide 04

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

CodeStatusWhat to Do
invalid_phone_number400Ask user to re-enter phone in E.164 format
rate_limit_exceeded429Wait and retry after the Retry-After header value
verification_expired410Send a new verification code
max_attempts_reached429Code entry locked — send new verification
insufficient_balance402Top up your account balance
unauthorized401Check your API key
carrier_unreachable503Retry 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.' };
  }
}
Tip: Never expose raw API error messages to end users. Map error codes to friendly, actionable messages in the user's language.
Guide 05

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

EndpointLimitWindow
POST /v1/verify100 requestsPer minute
POST /v1/verify/check300 requestsPer minute
GET /v1/verify/*600 requestsPer minute
Per phone number5 verificationsPer 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');
}
Need higher limits? Enterprise plans include custom rate limits. Contact sales@truecheck.co to discuss your requirements.
Guide 06

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 NumberBehavior
+15550000001Always succeeds — code is always 123456
+15550000002Always fails — simulates delivery failure
+15550000003Delayed delivery — delivered after 10 seconds
+15550000004Carrier error — simulates carrier rejection
+15550000005Expired — 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
CI/CD: Use sandbox mode in your CI pipeline. Set 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.