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/invitationsendpoint 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:
Authorization: Bearer sk_live_xxxxxxxxxxxxxxxxxxxx
Or via X-Api-Key header:
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
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)
{
"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
- Invitation is created with status
draft - Email/SMS delivery is triggered (single message with access code embedded in the link)
- Invitation is marked as
sent - If
callback_urlis provided, aninvitation.sentwebhook event is emitted
GET /api/v1/candidates/:id/results
Retrieve verification results for a completed verification.
Request
curl -X GET https://cloud.trua.com/api/v1/candidates/INV-12345/results \
-H "Authorization: Bearer sk_live_xxxxxxxxxxxxxxxxxxxx"
Response — Completed Verification (200 OK)
{
"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)
{
"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)
{
"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
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:
{
"event": "<event_type>",
"timestamp": "2026-02-03T14:30:00Z",
"relying_party_code": "GOLDCOAST",
"data": { ... }
}
Event Payload Details
invitation.sent
{
"candidate_id": "INV-12345",
"email": "john.doe@example.com",
"external_id": "CUST-12345",
"sent_at": "2026-02-03T10:00:00Z"
}
invitation.expired
{
"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
{
"candidate_id": "INV-12345",
"email": "john.doe@example.com",
"external_id": "CUST-12345",
"submitted_at": "2026-02-03T12:00:00Z"
}
submission.updated
{
"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
{
"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
{
"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.terminatedevent does not includeexternal_idto 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:
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
2xxwithin 30 seconds - Failed deliveries are retried up to 5 times with exponential backoff
- Check the
webhook_eventstable 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:
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)
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.
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.comwith your organization code and any relevantcandidate_idvalues