Webhooks

Slate notifies you of important events via webhooks. You configure your endpoints and subscribed events in the dashboard. Every event uses the same payload shape:

{
  "event_type": "credit-line.statement-ready",
  "data": { "id": "123e4567-e89b-12d3-a456-426614174000" },
  "timestamp": "2026-01-01T00:00:00Z"
}

data.id is the id of the object the event refers to (see the tables below). Webhooks are notifications, not payloads — always fetch the current state from the API after receiving one.

Verifying Webhooks

All webhook events are delivered using Svix and each request is cryptographically signed. You must verify signatures to ensure requests are authentic. Failing to verify webhook signatures may expose your system to spoofed or replayed requests.

Overview

Each webhook call includes three headers with additional information that are used for verification:

Header Description
svix-id The unique message identifier for the webhook message. This identifier is unique across all messages, but will be the same when the same webhook is being resent
svix-timestamp Unix timestamp (seconds)
svix-signature Base64 encoded list of signatures (space delimited)

Each webhook endpoint has a signing secret, which looks like: whsec_XXXXXXXXXXXXXXXXXXXXXXXXXXXX. You can get it from your webhook information on the dashboard. .

How Signatures Are Generated

Webhooks are signed using:

  • Algorithm: HMAC
  • Hash: SHA-256
  • Encoding: Base64

The signature is computed over the following string: ${svix_id}.${svix_timestamp}.${body};

The HMAC key is the Base64-decoded portion of your signing secret
(the part after the whsec_ prefix).

Signature Verification Steps

1. Read Required Headers

From the incoming request, extract:

svix-id
svix-timestamp
svix-signature

Also read the raw request body exactly as received.

⚠️ The body must not be modified (no JSON reformatting, whitespace changes, etc.).

2. Construct the Signed Content

{svix-id}.{svix-timestamp}.{raw_body}

Example:

msg_2N3R9J... . 1704839123 . {"event":"payment.completed","data":{...}}

3. Decode the Signing Secret

If your secret is:

whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw

Use only the part after whsec_ and Base64-decode it.

4. Compute the Expected Signature

Generate an HMAC-SHA256 signature and Base64-encode it.

5. Compare Against the svix-signature Header

The svix-signature header may contain multiple signatures, for example:

v1,abc123= v1,def456=

Rules:

  • Split by spaces.
  • Remove the version prefix (v1,).
  • Compare each signature.
  • If any signature matches, the webhook is valid.

6. Use Constant-Time Comparison

Always compare signatures using a constant-time comparison to prevent timing attacks.

Node.js Verification Example

import crypto from "crypto";

function verifyWebhook({
  payload,
  headers,
  signingSecret,
}) {
  const svixId = headers["svix-id"];
  const svixTimestamp = headers["svix-timestamp"];
  const svixSignature = headers["svix-signature"];

  if (!svixId || !svixTimestamp || !svixSignature) {
    throw new Error("Missing Svix headers");
  }

  const signedContent = `${svixId}.${svixTimestamp}.${payload}`;

  // Remove "whsec_" prefix and base64-decode the secret
  const secret = signingSecret.split("_")[1];
  const secretBytes = Buffer.from(secret, "base64");

  const expectedSignature = crypto
    .createHmac("sha256", secretBytes)
    .update(signedContent)
    .digest("base64");

  // svix-signature can contain multiple signatures
  const signatures = svixSignature.split(" ");

  for (const sig of signatures) {
    const [, signature] = sig.split(",");
    if (
      crypto.timingSafeEqual(
        Buffer.from(signature),
        Buffer.from(expectedSignature)
      )
    ) {
      return true;
    }
  }

  return false;
}

Resources

Event catalog

Customers & financial data

Event Fired when data.id
customer.created A new customer is created Customer
customer.updated A customer is updated Customer
business.created A new business is created Business
business.updated A business is updated Business
business.financial-data.attached Financial data is attached to a business Business

Pre-approvals

Event Fired when data.id
preapproval.created A new pre-approval (offer) is created Pre-approval
preapproval.expired A pre-approval expires Pre-approval
preapproval.consumed A pre-approval is consumed by an application Pre-approval

Applications

Event Fired when data.id
application.created A new application is created Application
application.submitted An application is submitted Application
application.bank.verified The application's bank account is verified Application
application.ready-to-accept The application is ready for the customer to accept and sign Application
application.approved An application is approved Application
application.rejected An application is rejected Application

Financing agreements

Event Fired when data.id
financing-agreement.created A new financing agreement is created Financing Agreement
financing-agreement.disbursed The agreement's funds are disbursed Financing Agreement
financing-agreement.repayment The agreement receives a repayment Financing Agreement
financing-agreement.completed The agreement is fully repaid Financing Agreement
financing-agreement.defaulted The agreement is defaulted Financing Agreement

Credit lines

Event Fired when data.id Type
credit-line.approved A credit line application is approved with a maximum facility limit Application both
credit-line.created A credit line is created (and, for revolving lines, its first period opens) Credit Line both
credit-line.suspended The line is suspended — an unpaid minimum on a revolving line, or an unpaid draw past its final payment date. Block the card / stop allowing draws Credit Line both
credit-line.reinstated A suspended line is reactivated — the card can be re-enabled and draws are accepted again Credit Line both
credit-line.statement-ready A billing statement is generated for a period Credit Line revolving
credit-line.draw.created A draw was created against the line. financingAgreementId carries the draw's agreement Credit Line draws
credit-line.draw.funded A draw's funds were transferred to the partner Credit Line draws
credit-line.draw.repaid A draw was fully repaid within its window Credit Line draws

Draws of a credit line are financing agreements, so they also emit the financing-agreement.* events above — created, disbursed, repayment and completed.

See the Revolving guide or the Draws guide for what to do on each event.

Finance requests (Early Wage Access)

Event Fired when data.id
finance-request.created A new finance request is created Finance Request
finance-request.in-progress The worker started the application Finance Request
finance-request.submitted The application is submitted Finance Request
finance-request.approved The request is approved Finance Request
finance-request.declined The request is declined Finance Request
finance-request.canceled The request is canceled Finance Request
finance-request.expired The request expires Finance Request

See the Early Wage Access guide for the full flow.