Skip to content

Webhooks

Validate incoming Sumsub webhook signatures to ensure payload integrity and authenticity.

Standalone Functions

The webhook validation functions are exported directly from the package and do not require a client instance. This is the recommended approach for webhook handlers.

ts
import { validateWebhook, isValidWebhook } from "sumsub-node-sdk";

validateWebhook

Returns a detailed validation result with digest comparison data. Throws if required headers are missing or the algorithm is unsupported.

ts
import { validateWebhook } from "sumsub-node-sdk";

const result = validateWebhook({
  secretKey: process.env.SUMSUB_WEBHOOK_SECRET_KEY,
  payload: requestBody, // Buffer or string
  headers: {
    "x-payload-digest": req.headers["x-payload-digest"],
    "x-payload-digest-alg": req.headers["x-payload-digest-alg"]
  }
});

if (result.isValid) {
  // Process the webhook
} else {
  // Reject — digest mismatch
  console.error("Expected:", result.calculatedDigest);
  console.error("Received:", result.receivedDigest);
}

isValidWebhook

Returns a simple boolean. Returns false (instead of throwing) on missing headers or unsupported algorithms.

ts
import { isValidWebhook } from "sumsub-node-sdk";

const valid = isValidWebhook({
  secretKey: process.env.SUMSUB_WEBHOOK_SECRET_KEY,
  payload: requestBody,
  headers: {
    "x-payload-digest": req.headers["x-payload-digest"],
    "x-payload-digest-alg": req.headers["x-payload-digest-alg"]
  }
});

if (!valid) {
  return res.status(401).json({ error: "Invalid signature" });
}

Interfaces

WebhookValidationOptions

ts
interface WebhookValidationOptions {
  /** Webhook-specific secret key (NOT the app secret key) */
  secretKey: string;
  /** Raw request body as Buffer or UTF-8 string */
  payload: Buffer | string;
  /** Required Sumsub webhook headers */
  headers: WebhookHeaders;
}

WebhookHeaders

ts
interface WebhookHeaders {
  "x-payload-digest": string;
  "x-payload-digest-alg": WebhookDigestAlgorithm;
}

WebhookValidationResult

ts
interface WebhookValidationResult {
  /** Whether the calculated digest matches the received digest */
  isValid: boolean;
  /** HMAC digest computed from the payload and secret key */
  calculatedDigest: string;
  /** Digest value from the x-payload-digest header */
  receivedDigest: string;
  /** Algorithm used for the digest */
  algorithm: WebhookDigestAlgorithm;
}

WebhookDigestAlgorithm

ts
type WebhookDigestAlgorithm =
  | "HMAC_SHA1_HEX" // Legacy — avoid for new integrations
  | "HMAC_SHA256_HEX" // Recommended
  | "HMAC_SHA512_HEX";

Required Headers

Sumsub sends two headers with every webhook request:

HeaderDescription
x-payload-digestHMAC hex digest of the request body
x-payload-digest-algAlgorithm used: HMAC_SHA256_HEX, HMAC_SHA512_HEX, or HMAC_SHA1_HEX

Both headers must be present for validation. validateWebhook throws an error if either is missing; isValidWebhook returns false.

Supported Algorithms

AlgorithmHashStatus
HMAC_SHA256_HEXSHA-256Recommended
HMAC_SHA512_HEXSHA-512Supported
HMAC_SHA1_HEXSHA-1Legacy (avoid for new integrations)

Express.js Example

A complete webhook handler with raw body parsing:

ts
import express from "express";
import { validateWebhook } from "sumsub-node-sdk";

const app = express();

// Sumsub webhooks require the raw body for signature validation.
// Parse JSON for all other routes, but capture raw body on the webhook path.
app.post("/webhooks/sumsub", express.raw({ type: "application/json" }), (req, res) => {
  try {
    const result = validateWebhook({
      secretKey: process.env.SUMSUB_WEBHOOK_SECRET_KEY,
      payload: req.body, // Buffer from express.raw()
      headers: {
        "x-payload-digest": req.headers["x-payload-digest"] as string,
        "x-payload-digest-alg": req.headers["x-payload-digest-alg"] as string
      }
    });

    if (!result.isValid) {
      return res.status(401).json({ error: "Invalid webhook signature" });
    }

    const event = JSON.parse(req.body.toString());

    switch (event.type) {
      case "applicantReviewed":
        // Handle verification completion
        break;
      case "applicantPending":
        // Handle applicant ready for review
        break;
      case "applicantCreated":
        // Handle new applicant
        break;
    }

    res.status(200).json({ ok: true });
  } catch (err) {
    console.error("Webhook validation error:", err.message);
    res.status(400).json({ error: err.message });
  }
});

Security Note

Use the webhook-specific secret key configured in Sumsub's webhook settings, not your application's API secret key. These are different credentials.

Client Instance Access

Webhook validation is also available via the client for consistency with other SDK methods:

ts
// Namespaced
sumsub.webhooks.validateWebhook(options);
sumsub.webhooks.isValidWebhook(options);

// Flat
sumsub.validateWebhook(options);
sumsub.isValidWebhook(options);

The client methods call the same underlying functions as the standalone exports. No HTTP client or authentication is required for webhook validation.

Webhooks has loaded