Employment Verification API Guide

This guide documents the APIs for integrating Trua Cloud's employment verification into your ATS, HRIS, or staffing platform. Use these endpoints to programmatically send candidate invitations, track their progress through the verification wizard, and retrieve completed results.

Your portal and API keys are at: https://trua.cloud/sites/{YOUR_CODE}

Same APIs we use: The Trua Cloud admin dashboard creates invitations through the same POST /api/v1/invitations endpoint available to you. Results are delivered through the same webhook infrastructure.

API Overview

Endpoint Method Purpose
/api/v1/invitations POST Create an invitation and send to candidate
/api/v1/candidates/:id/lifecycle GET Track candidate progress through the wizard
/api/v1/candidates/:id/results GET Retrieve completed verification results
/api/v1/webhooks/callback POST Receive lifecycle event notifications

Authentication

All API requests require an API key, provided via the Authorization header:

text
Authorization: Bearer sk_live_xxxxxxxxxxxxxxxxxxxx

Or via X-Api-Key header:

text
X-Api-Key: sk_live_xxxxxxxxxxxxxxxxxxxx

Key Types

Key Type Prefix Behavior
Test key sk_test_ Returns sandbox fixture responses; no emails sent
Live key sk_live_ Processes real requests; sends actual emails

API keys are generated in your site portal under API Keys. Keys are shown only once at generation time — store them securely. Contact your Trua Cloud account representative if you need a key rotated.


POST /api/v1/invitations

Create a new invitation for employment verification. The candidate receives an email (and optionally SMS) with a link to complete the verification wizard.

Request

bash
curl -X POST https://cloud.trua.com/api/v1/invitations \
  -H "Authorization: Bearer sk_live_xxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "candidate_email": "john.doe@example.com",
    "candidate_first_name": "John",
    "candidate_last_name": "Doe",
    "candidate_phone": "+15551234567",
    "external_id": "CUST-12345",
    "callback_url": "https://your-system.com/webhooks/trua"
  }'

Request Fields

Field Type Required Description
candidate_email string Yes Candidate's email address (must be valid format)
candidate_first_name string Yes Candidate's first name
candidate_last_name string Yes Candidate's last name
candidate_phone string No Phone number for SMS delivery (E.164 format, e.g., +15551234567)
external_id string No Your ATS/HRIS identifier for this candidate (must be unique per organization)
callback_url string No HTTPS URL to receive webhook events for this invitation
language string No Language preference: en (default) or es (Spanish)

Response (201 Created)

json
{
  "invitation_id": 12345,
  "candidate_id": "INV-12345",
  "status": "sent",
  "email": "john.doe@example.com",
  "external_id": "CUST-12345",
  "created_at": "2026-02-03T10:00:00Z"
}

Response Fields

Field Description
invitation_id Internal invitation identifier
candidate_id Public identifier for API queries (format: INV-{id})
status Invitation status — always sent on successful creation
email Candidate's email address
external_id Your external identifier (if provided)
created_at ISO 8601 timestamp of creation

Errors

Status Error Cause
401 Unauthorized Missing or invalid API key
403 Forbidden Facade-only customers cannot create invitations
422 candidate_email is required Missing required field
422 invalid email format Email validation failed
422 external_id 'X' already exists Duplicate external_id for this organization

What Happens After Creation

  1. Invitation is created with status draft
  2. Email/SMS delivery is triggered (single message with access code embedded in the link)
  3. Invitation is marked as sent
  4. If callback_url is provided, an invitation.sent webhook event is emitted

GET /api/v1/candidates/:id/results

Retrieve verification results for a completed verification.

Request

bash
curl -X GET https://cloud.trua.com/api/v1/candidates/INV-12345/results \
  -H "Authorization: Bearer sk_live_xxxxxxxxxxxxxxxxxxxx"

Response — Completed Verification (200 OK)

json
{
  "candidate_id": "INV-12345",
  "external_id": "CUST-12345",
  "status": "completed",
  "completed_at": "2026-02-01T14:30:00Z",
  "score": 92,
  "findings": [
    {
      "category": "employment",
      "entity": "Acme Corp",
      "status": "verified",
      "details": "Employment verified for dates 2022-01-15 to 2024-06-30"
    },
    {
      "category": "education",
      "entity": "State University",
      "status": "verified",
      "details": "Bachelor of Science in Computer Science, graduated 2019"
    },
    {
      "category": "residence",
      "entity": "123 Main St, Anytown",
      "status": "verified"
    }
  ]
}

Response — Verification In Progress (200 OK)

json
{
  "candidate_id": "INV-12345",
  "external_id": "CUST-12345",
  "status": "pending",
  "submitted_at": "2026-02-01T10:00:00Z",
  "message": "Verification in progress"
}

Response — Form Not Yet Submitted (200 OK)

json
{
  "candidate_id": "INV-12345",
  "external_id": "CUST-12345",
  "status": "in_progress",
  "message": "Form not yet submitted"
}

Status Values

Status Description
in_progress Candidate has not yet submitted the wizard
pending Form submitted, verification being processed
completed Verification complete, results available

Findings Object

When status is completed, the findings array contains individual verification results:

Field Description
category Type of verification: employment, education, residence, license
entity The organization, institution, or address verified
status Verification result: verified, unable_to_verify, discrepancy
details Additional context about the verification (optional)

GET /api/v1/candidates/:id/lifecycle

Track candidate progress through the Collect wizard. See the Candidate Progress API guide for complete documentation.

Quick Reference

bash
curl -X GET https://cloud.trua.com/api/v1/candidates/INV-12345/lifecycle \
  -H "Authorization: Bearer sk_live_xxxxxxxxxxxxxxxxxxxx"

Returns current wizard step, completion percentage, status transitions, and step-by-step progress.


Webhooks

Trua Cloud emits webhook events for key lifecycle transitions. Configure a callback_url on each invitation or set a global endpoint via your account representative.

Event Types

Event Trigger
invitation.sent Invitation email delivered to candidate
invitation.expired Invitation passed its expiration date (14 days)
submission.completed Candidate submitted the wizard
submission.updated Admin modified submission data
verification.completed Verification results available
verification.status_changed Verification status transitioned
data.retention_expiring Submission approaching retention limit (60 days)
data.terminated Submission data purged per retention policy (90 days)

Webhook Payload

Each event includes a standard envelope with event-specific data:

json
{
  "event": "<event_type>",
  "timestamp": "2026-02-03T14:30:00Z",
  "relying_party_code": "GOLDCOAST",
  "data": { ... }
}

Event Payload Details

invitation.sent

json
{
  "candidate_id": "INV-12345",
  "email": "john.doe@example.com",
  "external_id": "CUST-12345",
  "sent_at": "2026-02-03T10:00:00Z"
}

invitation.expired

json
{
  "invitation_id": 12345,
  "candidate_id": "INV-12345",
  "email": "john.doe@example.com",
  "external_id": "CUST-12345",
  "expired_at": "2026-02-10T00:00:00Z"
}

submission.completed

json
{
  "candidate_id": "INV-12345",
  "email": "john.doe@example.com",
  "external_id": "CUST-12345",
  "submitted_at": "2026-02-03T12:00:00Z"
}

submission.updated

json
{
  "candidate_id": "INV-12345",
  "email": "john.doe@example.com",
  "external_id": "CUST-12345",
  "updated_fields": ["employment_history", "personal_info"],
  "updated_at": "2026-02-03T14:00:00Z"
}

verification.completed

json
{
  "candidate_id": "INV-12345",
  "email": "john.doe@example.com",
  "external_id": "CUST-12345",
  "verified_at": "2026-02-03T14:30:00Z",
  "score": 92,
  "status": "completed"
}

verification.status_changed

json
{
  "candidate_id": "INV-12345",
  "email": "john.doe@example.com",
  "external_id": "CUST-12345",
  "old_status": "pending",
  "new_status": "completed",
  "changed_at": "2026-02-03T14:30:00Z"
}

data.retention_expiring

Sent at 60 days after submission as a warning that data will be purged at 90 days.
json
{
"candidate_id": "INV-12345",
"email": "john.doe@example.com",
"external_id": "CUST-12345",
"submitted_at": "2025-12-05T10:00:00Z",
"expires_at": "2026-03-05T10:00:00Z"
}

data.terminated

Sent at 90 days when submission data is purged per the retention policy.
json
{
"candidate_id": "INV-12345",
"external_id": "CUST-12345",
"terminated_at": "2026-03-05T00:00:00Z"
}

Note: The data.terminated event does not include email because PII has already been purged at this point. Use external_id to correlate with your records.

Webhook Headers

Header Description
Content-Type application/json
X-Webhook-Event Event type (e.g., verification.completed)
X-Webhook-Signature HMAC-SHA256 signature for verification
X-Webhook-Timestamp Unix timestamp of emission
User-Agent TruaCloud-Webhook/1.0

Signature Verification

Verify webhook authenticity by computing the HMAC-SHA256 of the raw request body using your webhook secret:

ruby
expected = OpenSSL::HMAC.hexdigest('SHA256', webhook_secret, request_body)
return 401 unless Rack::Utils.secure_compare(expected, signature_header)

Reject requests where the timestamp is more than 5 minutes old to prevent replay attacks.

Delivery and Retries

  • Webhooks are delivered via HTTP POST to your callback_url
  • Your endpoint must return 2xx within 30 seconds
  • Failed deliveries are retried up to 5 times with exponential backoff
  • Check the webhook_events table in admin to see delivery status

Sandbox Testing

Use test keys (sk_test_) to develop and test your integration without sending real emails or processing real data.

Sandbox Responses

Endpoint Test Key Behavior
POST /api/v1/invitations Returns fixture response; no email sent
GET /api/v1/candidates/:id/results Returns completed verification fixture
GET /api/v1/candidates/pending/results Returns pending verification fixture
GET /api/v1/candidates/404/results Returns 404 not found

Test Harness

Your site portal includes an interactive Test Harness where you can try all endpoints with your test key without writing code.


Integration Example: Complete Flow

Here's a typical integration flow:

text
1. Your system creates invitation via API
   POST /api/v1/invitations → receives candidate_id

2. Candidate receives email, completes wizard
   (webhook: invitation.sent)
   (webhook: submission.completed)

3. Your system polls for progress (optional)
   GET /api/v1/candidates/:id/lifecycle

4. Verification completes
   (webhook: verification.status_changed — pending → completed)
   (webhook: verification.completed)

5. Your system retrieves full results
   GET /api/v1/candidates/:id/results

Code Example (Ruby)

ruby
require 'net/http'
require 'json'

class TruaClient
  API_BASE = 'https://cloud.trua.com/api/v1'

  def initialize(api_key)
    @api_key = api_key
  end

  def create_invitation(email:, first_name:, last_name:, external_id: nil)
    post('/invitations', {
      candidate_email: email,
      candidate_first_name: first_name,
      candidate_last_name: last_name,
      external_id: external_id
    })
  end

  def get_results(candidate_id)
    get("/candidates/#{candidate_id}/results")
  end

  private

  def post(path, body)
    uri = URI("#{API_BASE}#{path}")
    request = Net::HTTP::Post.new(uri)
    request['Authorization'] = "Bearer #{@api_key}"
    request['Content-Type'] = 'application/json'
    request.body = body.to_json
    execute(uri, request)
  end

  def get(path)
    uri = URI("#{API_BASE}#{path}")
    request = Net::HTTP::Get.new(uri)
    request['Authorization'] = "Bearer #{@api_key}"
    execute(uri, request)
  end

  def execute(uri, request)
    http = Net::HTTP.new(uri.host, uri.port)
    http.use_ssl = true
    response = http.request(request)
    JSON.parse(response.body)
  end
end

# Usage
client = TruaClient.new('sk_live_xxxxxxxxxxxxxxxxxxxx')
result = client.create_invitation(
  email: 'john@example.com',
  first_name: 'John',
  last_name: 'Doe',
  external_id: 'CUST-12345'
)
puts "Created: #{result['candidate_id']}"

Rate Limiting

API requests are rate-limited to 100 requests per minute per API key.

text
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 87
X-RateLimit-Reset: 1737984000

Exceeding the limit returns 429 Too Many Requests. Wait for the retry_after period before retrying.


Getting Help

  • Your Portal: API keys, test harness, and documentation at https://trua.cloud/sites/{YOUR_CODE}
  • Related Guides: Employment Claim Collection Guide · Employment Verification Workflow
  • Account Representative: Contact for key rotation, webhook configuration, and go-live coordination
  • Support: Email support@trua.com with your organization code and any relevant candidate_id values