Gareth Wilson Gareth Wilson

Guide to Certn Webhooks: Features and Best Practices

Published


Certn webhooks keep a partner's systems up to date with background checks as they progress. Certn runs criminal record, identity, employment and reference checks, and when an applicant's report changes, it posts the updated applicant to your webhook URL so your hiring or onboarding workflow can move on without polling the API.

This guide covers how Certn webhooks are enabled and signed, what a delivery contains, how retries behave, and the practices that keep a Certn receiver reliable.

What are Certn webhooks?

Certn has one webhook URL per partner. Every update for every applicant goes to it as a POST, and partner resellers share the same URL and secret across all their accounts and sub-accounts. You can't choose events, because Certn doesn't have any: the body is the applicant object itself, and the state of the background check lives in fields such as status and report_status.

Signing is opt-in. Once enabled, each request carries a Certn-Signature header that works like Stripe's: a t= timestamp and one or more v1= signatures, each a hex HMAC-SHA256 of <t>.<raw body>.

Certn webhook features

FeatureDetails
ConfigurationThe Partner page: enter the URL and click Enable Integration
ScopeOne URL and one secret per partner, shared by resellers' accounts and sub-accounts
Event selectionNone. Every applicant update is sent
Event typeNone. The body is the applicant object
Signature headerCertn-Signature: t=<unix seconds>,v1=<hex>[,v1=<hex>], on one line
SchemeHMAC-SHA256, hex digest. v1 is the only live scheme
Signed content<t>.<raw body>
SigningOpt-in
SecretA secret string on the Partner page, used as UTF-8
Retries408, 500, 502, 503 and 504: up to three more attempts, with growing waits
Not retriedAny other status above 399
Source IPsNot published

Common events

There is no event catalogue. Each delivery is the full applicant object, with about 150 top-level keys in Certn's example. These are the ones that tell you what has changed:

FieldExample valueMeaning
id1ba124f1-d473-44c0-a066-d623b2cb6253The applicant ID, which correlates updates
short_uidpmndgAeHA short reference for the applicant
status / status_labelReturnedThe applicant's state
report_statusCOMPLETEProgress of the whole report
order_statusCOMPLETEProgress of the order
result / result_labelCLEAREDThe overall result
*_resultobjects or nullOne per check, such as us_criminal_record_check_result, equifax_result and reference_result

Treat the body as a snapshot of the applicant, not as a description of one change. Compare it with what you stored last time to work out what happened, and branch on the status fields rather than on anything that names an event.

Setting up Certn webhooks

  1. In Certn, open the Partner tab.
  2. Enter your webhook URL.
  3. Click Enable Integration.
  4. Copy the secret string and store it server-side, for example as CERTN_WEBHOOK_SECRET.

The URL can be changed at any time. Certn's setup instructions say the secret can't be changed and to contact support if it is compromised. The same page later describes rolling a secret from the dashboard with a 48-hour overlap, so check with Certn which applies to your account.

Signed payloads are opt-in, and Certn's documentation doesn't say where the switch is. Ask your Certn contact to turn signing on before you rely on it. When you receive Certn webhooks through Hookdeck, signing has to be on: create a Certn source, paste the signing secret, and note that unsigned deliveries fail verification.

Securing Certn webhooks

Split Certn-Signature on commas, trim each element, take t and every v1, and ignore other schemes. Compute an HMAC-SHA256 of <t>.<raw body> with the secret as a UTF-8 key, and accept the request if any v1 matches in constant time.

const crypto = require('crypto');
const express = require('express');

function verifyCertnSignature(rawBody, header, secret) {
  if (!header || !secret) return false;
  let t;
  const candidates = [];
  for (const part of header.split(',')) {
    const [key, ...rest] = part.trim().split('=');
    if (key.trim() === 't') t = rest.join('=').trim();
    else if (key.trim() === 'v1') candidates.push(rest.join('=').trim());
  }
  if (!/^\d+$/.test(t || '') || candidates.length === 0) return false;

  const expected = Buffer.from(
    crypto.createHmac('sha256', secret).update(`${t}.`).update(rawBody).digest('hex'),
    'utf8'
  );
  return candidates.some((candidate) => {
    const received = Buffer.from(candidate.toLowerCase(), 'utf8');
    return received.length === expected.length && crypto.timingSafeEqual(received, expected);
  });
}

const app = express();

app.post('/webhooks/certn', express.raw({ type: '*/*' }), (req, res) => {
  const secret = process.env.CERTN_WEBHOOK_SECRET;
  if (!secret) return res.sendStatus(500); // fail closed

  if (!verifyCertnSignature(req.body, req.get('certn-signature'), secret)) {
    return res.sendStatus(401);
  }

  const applicant = JSON.parse(req.body.toString('utf8'));
  // Store the snapshot keyed by applicant.id, then act on report_status asynchronously.
  res.sendStatus(200);
});

The same check in Python:

import hashlib
import hmac


def verify_certn_signature(raw_body: bytes, header, secret: str) -> bool:
    if not header or not secret:
        return False
    t, candidates = None, []
    for part in header.split(","):
        key, sep, value = part.strip().partition("=")
        if sep and key.strip() == "t":
            t = value.strip()
        elif sep and key.strip() == "v1":
            candidates.append(value.strip())
    if not t or not (t.isascii() and t.isdigit()) or not candidates:
        return False
    expected = hmac.new(
        secret.encode("utf-8"), t.encode("utf-8") + b"." + raw_body, hashlib.sha256
    ).hexdigest().encode("utf-8")
    return any(hmac.compare_digest(c.lower().encode("utf-8"), expected) for c in candidates)

Certn says you can reject a payload whose timestamp is too old, but names no tolerance. The Certn webhook skill leaves that window off by default, so it accepts Hookdeck retries and replays of older events. If you turn the window on with CERTN_TIMESTAMP_TOLERANCE_SECONDS, note that Certn signs each of its own retries afresh, but Hookdeck retries and replays resend the original t, so any that arrive after the window has passed fail the check.

Certn's docs warn that Express doesn't give you the original body string without extra middleware. express.raw() on the route solves that.

Make Certn webhooks production-ready. Hookdeck Event Gateway verifies the Certn-Signature header, stores every applicant update, and delivers it to your services with retries and replay you control.

Certn webhook limitations and pain points

Unsigned until you opt in

The Problem: Deliveries arrive with no Certn-Signature header, so there is nothing to verify.

Why It Happens: Signing is optional and off unless it has been enabled for your partner account, and the documentation doesn't say where to turn it on.

Workarounds:

  • Ask Certn to enable signed payloads before you go live.
  • Reject requests without the header once signing is on, so a forged unsigned request isn't accepted.

How Hookdeck Can Help: Hookdeck's Certn source type expects the header. Unsigned deliveries are marked as failed verification and aren't delivered to your destinations, which tells you straight away that signing is off.

No event types

The Problem: You can't route or filter on what happened, only on the applicant's current state.

Why It Happens: Certn posts the applicant object whenever it changes. No field or header says "report completed" or "check failed".

Workarounds:

  • Store the last snapshot per applicant id and diff the status fields to decide what changed.
  • Act on terminal states, such as report_status of COMPLETE, and treat earlier snapshots as progress.

How Hookdeck Can Help: Hookdeck filters can match on body fields such as report_status, so only completed reports reach the service that acts on them.

Few retries, and some errors are final

The Problem: An update is lost after a short outage, or after a single 4xx response.

Why It Happens: Certn retries only 408, 500, 502, 503 and 504, up to three more times. Any other status above 399, including 401, 404 or 429, ends delivery on the first attempt.

Workarounds:

  • Return 5xx, not 4xx, for transient problems on your side.
  • Read the applicant from the Certn API after an outage to catch missed updates.

How Hookdeck Can Help: Hookdeck accepts each delivery as soon as it arrives and stores it, so your own errors and downtime don't end Certn's delivery. It then delivers to your service with automatic retries.

Best practices

Verify against the raw body

The signed string uses the request body exactly as sent. Use express.raw() in Express, await request.text() in a Next.js route handler and await request.body() in FastAPI, and parse only after the check passes.

Accept any matching v1

During a secret roll, Certn sends one v1 per active secret. Check each one and accept the request if any matches.

Make processing idempotent

There is no event ID. Key updates on the applicant id together with the status fields or modified, and make sure applying the same snapshot twice has no extra effect. See our guide to webhook idempotency.

Protect the payload

The applicant object contains personal data and check results. Keep the endpoint on HTTPS, restrict who can read stored payloads, and drop fields you don't need.

Acknowledge fast and process asynchronously

Verify, store the snapshot, return 200, and do the work afterwards. See why to process webhooks asynchronously.

Conclusion

Certn posts the whole applicant object to one partner-wide URL, with no event types, and signs it only once signing is enabled. Verify Certn-Signature against <t>.<raw body> and accept any matching v1, keep timestamp checks optional, route on report_status and the other status fields, and return 5xx rather than 4xx for temporary failures so Certn retries.

Hookdeck Event Gateway sits in front of your receiver, checks every Certn signature, and holds each applicant update until your service has processed it.

Get started with Hookdeck for free and handle Certn webhooks reliably in minutes.


Gareth Wilson

Gareth Wilson

Product Marketing

Multi-time founding marketer, Gareth is PMM at Hookdeck and author of the newsletter, Community Inc.