Guide to Truework Webhooks: Features and Best Practices
Truework webhooks tell you when an income or employment verification has moved on: an order has finished, or a single verification request inside it has changed state. If you order verifications through the Truework API, webhooks save you from polling each order until its results are ready.
This guide covers how Truework webhooks are created and authenticated, the two events and their payloads, what Truework leaves undocumented, and the practices that keep a Truework receiver reliable.
What are Truework webhooks?
A Truework webhook is a subscription created in your service account's Developer Settings. It has an HTTPS URL, an optional description, and one or more event types. When a subscribed event happens, Truework sends a JSON POST to the URL.
Truework does not sign webhook payloads. Instead, each webhook has its own authentication token, shown in Developer Settings, and Truework sends that token unchanged in the X-Truework-Token header on every request. Your receiver authenticates a request by comparing the header with the token it expects. Truework also publishes the IP addresses it sends from, which you can use as a second check.
Truework webhook features
| Feature | Details |
|---|---|
| Configuration | Developer Settings > Developer > Webhooks > Sandbox or Production |
| Events | order.completed and verification_ |
| Subscriptions | One webhook can subscribe to several event types |
| Limit | Ten webhooks per service account |
| Authentication | Per-webhook token, sent verbatim in X-Truework-Token |
| Body signature | None |
| Replay window | None. No timestamp is sent or signed |
| Versioning | Each subscription is to a version of the event. Upgrades are one-way |
| Retries | Failed webhooks are retried periodically for 48 hours |
| Failure alerts | Emails to the service account address |
| Duplicates | Possible. Handlers should be idempotent |
| Source IPs | Production 18.218.101.165, 3.133.214.186, 3.23.196.239; Sandbox 3.22.163.100 |
Truework says its IP addresses are subject to change and that it will try to announce changes ahead of time. It does not document a response timeout, a retry interval or what counts as a failed delivery.
Common events
Truework has two webhook events. Which one you need depends on the verification method you use.
| Event | Fires when | Recommended for |
|---|---|---|
order.completed | Every verification on the order has been completed or canceled | Credentials and Documents verifications |
verification_ | A verification request within the order changes state | Smart Outreach |
Instant verifications return their result straight away and need no webhook.
Every payload has three top-level keys: version, hook and data. The event name is in hook.event, alongside the webhook's numeric id, its target URL and triggered_at. Truework publishes field lists and a JSON schema for each event, but no example body. Built from those field lists, an order.completed delivery looks like this:
{
"version": "<subscription version>",
"hook": {
"id": 123,
"event": "order.completed",
"target": "https://example.com/webhooks/truework",
"triggered_at": "<datetime>"
},
"data": {
"order_id": "<order id>",
"metadata": {}
}
}
When you receive it, fetch the order's results from the API. verification_ adds verification_request_id, state and credentials_ to data. state is one of pending-approval, processing, action-required, completed, canceled or invalid. A completed state means new data is available on the order. A canceled state means the verification could not be completed but may still hold information or documents from the applicant.
action-required means Smart Outreach needs more information. You can supply it in the Truework app, or have your integration cancel the order when this state arrives.
Setting up Truework webhooks
- Sign in to your service account and open the developer settings page.
- Select < > Developer in the left-hand panel, scroll to Webhooks, and click Edit under Sandbox or Production.
- Choose the events in the subscriptions dropdown, enter the URL, and optionally add a description.
- Click Create.
- Copy the webhook's authentication token and store it server-side, for example as
TRUEWORK_WEBHOOK_TOKEN.
Sandbox and Production webhooks are configured separately and have different tokens. If you delete a webhook and create a new one with the same URL, the new one gets a different token, so update your receiver at the same time.
If you receive through Hookdeck, create a Truework source, use its URL as the webhook URL, and paste the token into the source's Authentication Token field. Hookdeck accepts each request, then records any request whose X-Truework-Token does not match as failed verification, so it never becomes an event for your service.
Securing Truework webhooks
There is nothing to compute. Read X-Truework-Token and compare it with your stored token in constant time. Fail closed if no token is configured, so an empty header can never match an empty secret.
const crypto = require('crypto');
const express = require('express');
function verifyTrueworkToken(headerValue, expectedToken) {
if (!headerValue || !expectedToken) return false;
const received = Buffer.from(headerValue, 'utf8');
const expected = Buffer.from(expectedToken, 'utf8');
return received.length === expected.length && crypto.timingSafeEqual(received, expected);
}
const app = express();
app.post('/webhooks/truework', express.raw({ type: '*/*' }), (req, res) => {
const token = process.env.TRUEWORK_WEBHOOK_TOKEN;
if (!token) return res.sendStatus(500); // fail closed
if (!verifyTrueworkToken(req.get('x-truework-token'), token)) {
return res.sendStatus(401);
}
let event;
try {
event = JSON.parse(req.body.toString('utf8'));
} catch {
return res.sendStatus(400);
}
// Route on event.hook.event. Answer 200 to events you don't handle.
res.sendStatus(200);
});
The same check in Python:
import hmac
def verify_truework_token(header_value, expected_token: str) -> bool:
if not header_value or not expected_token:
return False
return hmac.compare_digest(header_value.encode("utf-8"), expected_token.encode("utf-8"))
# In FastAPI: verify_truework_token(request.headers.get("x-truework-token"),
# os.environ["TRUEWORK_WEBHOOK_TOKEN"])
The token travels with every request, so anyone who captures one can send forged requests until you rotate it. Keep the endpoint on HTTPS, and if your network allows it, restrict inbound traffic to Truework's published IP addresses as well.
Make Truework webhooks production-ready. Hookdeck Event Gateway checks the
X-Truework-Tokenheader, answers Truework straight away, and durably queues every order and verification event before your service sees it.
Truework webhook limitations and pain points
A static token instead of a signature
The Problem: Nothing protects the body from being changed, and a captured request can be replayed.
Why It Happens: Truework sends the token itself rather than a signature computed over the body. There is no timestamp, so nothing tells a fresh request from an old one.
Workarounds:
- Use HTTPS only and allowlist Truework's source IP addresses.
- Treat the webhook as a notification: fetch the order from the Truework API before you act on it, which Truework recommends anyway for
order.completed.
How Hookdeck Can Help: Hookdeck holds the token and checks it on every request, so your own services don't need it, and requests with a missing or wrong token never reach them.
Recreated webhooks break authentication
The Problem: After cleaning up webhooks in Developer Settings, every delivery fails authentication.
Why It Happens: Tokens belong to webhook instances. A new webhook for the same URL gets a new token, and Sandbox and Production webhooks have different tokens too.
Workarounds:
- Update the stored token whenever you recreate a webhook.
- Keep separate configuration for Sandbox and Production, and label each webhook with a description.
How Hookdeck Can Help: With one Hookdeck source per environment, you update the token in one place, and the request log shows clearly when verification starts failing.
Version upgrades can change the payload
The Problem: A subscription upgrade changes field names or structure under your handler.
Why It Happens: Subscriptions are tied to an event version. New subscriptions always use the latest version, upgrades can't be undone, and Truework only promises that the top-level version field stays the same across versions.
Workarounds:
- Log
versionon every delivery and branch on it if you run more than one. - Test an upgrade in Sandbox before applying it in Production.
How Hookdeck Can Help: Hookdeck stores the full body of every request, so you can compare payloads before and after an upgrade and replay old ones against new code.
Little documented delivery behaviour
The Problem: You don't know how long Truework waits for a response, how often it retries, or what it counts as a failure.
Why It Happens: Truework says only that failed webhooks are retried periodically for 48 hours and that failures are emailed to the service account.
Workarounds:
- Respond with
200as soon as the token checks out, and do the work afterwards. - Make sure the service account's email reaches someone who will act on it.
- After an outage longer than 48 hours, reconcile open orders through the API.
How Hookdeck Can Help: Hookdeck accepts each delivery when it arrives, so Truework's 48-hour clock stops mattering, and retries delivery to your service on a schedule you set.
Inconsistent state spelling
The Problem: A handler that checks for action-required might miss a state.
Why It Happens: The webhooks overview spells the state action-required, while the event reference page lists action-requred. Neither page shows a real payload.
Workarounds:
- Accept both spellings until you have seen a real delivery with this state.
- Log and alert on any
statevalue you don't recognise.
How Hookdeck Can Help: Hookdeck filters can match several values for data.state, so you can route either spelling to the same destination.
Best practices
Answer 200 to events you don't handle
Truework says handlers should ignore unknown events but still respond with 200. Otherwise, adding a subscription can cause errors, retries and failure emails.
Deduplicate deliveries
Truework says an event can be sent more than once, and documents no event ID. hook.id identifies the webhook, not the event. Build a key from hook.event, data.order_id, data.verification_ and data.state, and store it before processing. See our guide to webhook idempotency.
Fetch results from the API
Payloads carry IDs and states, not verification results. After order.completed, or a completed state, fetch the order from the Truework API.
Pick the event that matches your verification method
Use order.completed for Credentials and Documents verifications, and verification_ when you use Smart Outreach and need to act on action-required.
Acknowledge fast and process asynchronously
Verify the token, store the event, return 200, and do the work afterwards. See why to process webhooks asynchronously.
Conclusion
Truework webhooks are unsigned JSON POSTs, authenticated by a per-webhook token in X-Truework-Token. Compare the token in constant time, allowlist Truework's IP addresses where you can, dispatch on hook.event, answer 200 to anything you don't recognise, deduplicate, and fetch results from the API rather than trusting the payload alone.
Hookdeck Event Gateway sits in front of your receiver, checks the Truework token, and holds each event until your service has processed it.
Get started with Hookdeck for free and handle Truework webhooks reliably in minutes.