# 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_request.state.change` |
| 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_request.state.change` | 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:

```json
{
  "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_request.state.change` adds `verification_request_id`, `state` and `credentials_session_token` 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

1. Sign in to your service account and open the [developer settings page](https://app.truework.com/requester/developer-settings).
2. Select **< > Developer** in the left-hand panel, scroll to **Webhooks**, and click **Edit** under **Sandbox** or **Production**.
3. Choose the events in the subscriptions dropdown, enter the URL, and optionally add a description.
4. Click **Create**.
5. 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.

```javascript
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:

```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](/event-gateway) checks the `X-Truework-Token` header, 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 `version` on 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 `200` as 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 `state` value 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_request_id` and `data.state`, and store it before processing. See our [guide to webhook idempotency](/webhooks/guides/implement-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_request.state.change` 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](/webhooks/guides/why-implement-asynchronous-processing-webhooks).

## Conclusion

Truework webhooks are unsigned JSON `POST`s, 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](https://hookdeck.com) sits in front of your receiver, checks the Truework token, and holds each event until your service has processed it.

[Get started with Hookdeck](https://dashboard.hookdeck.com/signup) for free and handle Truework webhooks reliably in minutes.