Support

Get help and stay up to date with Credlab

← Back

API Access: Webhooks & Signature Verification

How To
API Access

Webhooks

Webhook endpoints are configured per-account under Settings → Webhooks. When you create an endpoint you provide:

  • URL — must be HTTPS, and must not point to a private/internal/loopback host (localhost, .local/.internal suffixes, and private/loopback/ link-local IPs are all rejected).

Credlab generates a unique signing secret for each endpoint when it’s created; it’s used to sign every delivery (see Verifying signatures). The secret is available any time on the endpoint’s page (Settings → Webhooks → your endpoint).

How sends happen

Webhooks are staff-initiated, not automatic. A credit application’s dashboard shows a Send to menu listing the account’s active webhook endpoints; choosing one queues one delivery of the credit_application.decided event to that endpoint. Staff typically send after recording a decision, but a send can happen at any point — if no decision has been recorded yet, the payload’s approval_status and approval_decided_at are null (or a non-decision status such as pending).

Staff resending the same application after a correction is normal — a correction to a decision, or simply re-pushing a decision, produces another delivery for the same credit_application.id. Your receiver should process each delivery idempotently: upsert by credit_application.id (the newest delivery for a given id wins), and use the X-Credlab-Delivery header to dedupe delivery attempts that are retries of the same send rather than independent sends.

Event: credit_application.decided

This event carries a thin notification, not the full application — it tells you what changed and where to fetch it, not the data itself. The request is an HTTP POST to your configured URL.

Example payload body:

{
  "event": "credit_application.decided",
  "credit_application": {
    "id": "ca_sample000000",
    "approval_status": "approved",
    "approval_decided_at": "2026-01-02T00:00:00Z"
  },
  "account_id": "acct_sample000000"
}

On receipt

  1. Verify the X-Credlab-Signature header (see Verifying signatures).
  2. Fetch the full record: GET /api/v1/credit_applications/<credit_application.id>?account_id=<account_id>, using the ids from the payload body.
  3. Upsert the fetched record into your system, keyed on credit_application.id.

Request headers

Header Description
Content-Type Always application/json.
X-Credlab-Event The event type, e.g. credit_application.decided (or test — see the Testing section).
X-Credlab-Delivery A unique id for this delivery attempt’s underlying delivery record (e.g. whd_1a2b3c4d5e6f). This id stays the same across retries of the same delivery — use it as an idempotency key so a retried delivery isn’t double-processed.
X-Credlab-Signature The HMAC signature of this request. See Verifying signatures.

Verifying signatures

Every webhook request includes an X-Credlab-Signature header in the form:

t=<unix timestamp>,v1=<hex-encoded HMAC-SHA256 digest>

The digest is computed over the string "<t>.<raw request body>", using HMAC-SHA256 keyed with the endpoint’s signing secret (available any time on the endpoint’s page, under Settings → Webhooks → your endpoint).

To verify a delivery:

received  = header "X-Credlab-Signature"        # "t=1730000000,v1=abc123..."
t         = value after "t="
v1        = value after "v1="
expected  = hex( HMAC_SHA256( key: signing_secret, data: t + "." + raw_request_body ) )
valid     = constant_time_equal(expected, v1) AND (now - t) < 300 seconds

Important details:

  • Use the raw, unparsed request body (exact bytes as received) when computing the digest — re-serializing parsed JSON can change whitespace and break the signature.
  • Use a constant-time comparison to avoid timing attacks.
  • Reject requests where the timestamp is too old (a 5-minute/300-second tolerance is recommended) to guard against replay of captured requests.

Delivery behavior

  • Your endpoint must respond with a 2xx status within 10 seconds. Slower responses are treated as a timeout.
  • If your endpoint responds with a non-2xx status, times out, or the connection fails, Credlab retries the delivery with increasing backoff, up to 8 attempts total, spread over roughly 1.5 hours.
  • If all attempts are exhausted without a successful 2xx response, the delivery is marked failed. Failed and past deliveries are visible in the delivery log on the webhook endpoint’s page under Settings → Webhooks, showing each delivery’s event, state, most recent response code, attempt count, and timestamp.
  • Because retries can occur, your receiver should be idempotent: use the X-Credlab-Delivery header value to detect and ignore a delivery you’ve already processed.
  • Webhooks are a best-effort, at-least-once push mechanism, not a guaranteed log. If your receiver has downtime longer than the retry window, or you suspect a gap, use the read API’s decided_since filter to poll for and backfill any decisions you may have missed.