Agent skill

Sumsub Webhooks Skill

Receive and verify Sumsub webhooks. Use when setting up Sumsub KYC / identity verification webhook handlers, debugging x-payload-digest signature verification (hex HMAC of the raw body, algorithm from x-payload-digest-alg: HMAC_SHA256_HEX, HMAC_SHA512_HEX or legacy HMAC_SHA1_HEX), or handling events like applicantReviewed, applicantPending, applicantCreated, applicantOnHold and applicantActionReviewed.

Install this skill

npx skills add hookdeck/webhook-skills --skill sumsub-webhooks


When to Use This Skill

  • How do I receive Sumsub webhooks?
  • How do I verify the Sumsub x-payload-digest webhook signature?
  • Which hash do I use: SHA1, SHA256 or SHA512 (x-payload-digest-alg)?
  • How do I handle applicantReviewed (GREEN / RED) and applicantPending events?
  • Why is my Sumsub webhook signature verification failing?

Verification (core)

Sumsub signs the raw body with HMAC keyed on the webhook's Secret key and sends the hex digest in x-payload-digest. The algorithm is set per webhook and announced in x-payload-digest-alg. Always read that header: webhooks created before SHA256 became the default still send HMAC_SHA1_HEX. There is no timestamp and no Standard Webhooks headers.

const crypto = require('crypto');
const ALGS = { HMAC_SHA1_HEX: 'sha1', HMAC_SHA256_HEX: 'sha256', HMAC_SHA512_HEX: 'sha512' };

function verifySumsubWebhook(rawBody, digest, digestAlg, secret) {
  const algorithm = ALGS[digestAlg || 'HMAC_SHA256_HEX']; // absent = SHA256, unknown = reject
  if (!digest || !secret || !algorithm) return false;
  const expected = crypto.createHmac(algorithm, secret).update(rawBody).digest('hex');
  try {
    return crypto.timingSafeEqual(Buffer.from(digest.trim().toLowerCase()), Buffer.from(expected));
  } catch {
    return false; // length mismatch
  }
}
// verifySumsubWebhook(req.body /* Buffer */, req.headers['x-payload-digest'],
//                     req.headers['x-payload-digest-alg'], process.env.SUMSUB_WEBHOOK_SECRET)

Python: hmac.compare_digest(digest.lower(), hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()), with hashlib.sha1 / sha512 picked from the same header.

For complete handlers with tests, see examples/express/, examples/nextjs/, examples/fastapi/.

Sumsub has no official SDK helper for webhook verification, so every example verifies manually.

Common Event Types

The event name is in the body's type field (there is no event-type header).

Event (type)Triggered When
applicantCreatedApplicant record is created (SDK init, verification link, API)
applicantPendingApplicant submits documents; review status becomes pending
applicantReviewedFinal result: reviewResult.reviewAnswer is GREEN or RED (reviewRejectType RETRY / FINAL)
applicantOnHoldReview needs manual/compliance attention (onHold)
applicantActionPendingAn applicant action (e.g. payment method check) becomes pending
applicantActionReviewedAn applicant action receives its result
applicantResetApplicant is reset for re-verification
applicantDeletedApplicant is deleted

For the full list (applicantLevelChanged, applicantWorkflowCompleted, transaction monitoring applicantKytTxn*, case management kytCase* and more), see references/overview.md and Sumsub's user verification webhooks.

Important Headers

HeaderDescription
x-payload-digestHex HMAC of the raw body, keyed with the webhook's secret key
x-payload-digest-algHMAC_SHA256_HEX (default), HMAC_SHA512_HEX, or HMAC_SHA1_HEX (legacy)

Gotchas

  • Use the raw body bytes. Re-serialised JSON produces a different digest.
  • Don't hard-code SHA256. Legacy webhooks send SHA1; pick the hash from x-payload-digest-alg.
  • Return 400, not 401, for a bad digest. Sumsub treats 401/403/404 as permanent and stops retrying; other non-2xx codes are retried 5 times.
  • Respond within 10 seconds (connect within 1 second) or the delivery counts as a timeout.
  • testMode: true marks deliveries sent with Test webhook; sandboxMode: true marks the Sandbox environment.

Environment Variables

SUMSUB_WEBHOOK_SECRET=your_sumsub_webhook_secret_key   # Webhook manager -> webhook -> Secret key

Local Development

# Start tunnel (no account needed)
npx hookdeck-cli listen 3000 sumsub --path /webhooks/sumsub

Reference Materials


Repository

hookdeck/webhook-skills

v0.1.0 · MIT · Updated Oct 28, 2026

View on GitHub →