Agent skill

ZoomInfo Webhooks Skill

Receive and verify ZoomInfo webhooks. Use when setting up ZoomInfo webhook handlers, checking the x-zoominfo-token verification token, completing the Validate step, or handling contact and company update events from the ZoomInfo Monitoring API.

Install this skill

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


When to Use This Skill

  • How do I receive ZoomInfo webhooks?
  • How do I verify the x-zoominfo-token header on a ZoomInfo webhook?
  • Why does ZoomInfo's Validate step fail or send a request without a token?
  • How do I handle ZoomInfo contact or company update events?
  • How do I rotate a ZoomInfo webhook verification token?

Authentication Model

ZoomInfo webhooks are not signed. There is no HMAC and no signed timestamp. When you create a webhook, ZoomInfo generates a verification token (you can regenerate it any time) and sends it in the x-zoominfo-token header of every POST to your target URL. Compare it with the stored token in constant time.

Two things to know:

  • The Validate button posts a sample payload before the token exists. Your endpoint must accept it (the examples return 200 without processing when ZOOMINFO_WEBHOOK_TOKEN is unset), then you generate the token, store it, and activate the webhook.
  • Set ZOOMINFO_WEBHOOK_TOKEN before you activate the webhook. While it is unset the examples acknowledge every request without processing it, so an unset token in production means requests are accepted but never authenticated or handled.
  • Because the token travels with every request, serve the endpoint over HTTPS only.

Verification (core)

const crypto = require('crypto');

function verifyZoomInfoToken(tokenHeader, expectedToken) {
  if (!tokenHeader || !expectedToken) return false;
  const received = Buffer.from(tokenHeader);
  const expected = Buffer.from(expectedToken);
  // timingSafeEqual throws on length mismatch, so check length first
  return received.length === expected.length && crypto.timingSafeEqual(received, expected);
}

// headers['x-zoominfo-token'] vs process.env.ZOOMINFO_WEBHOOK_TOKEN
import hmac

def verify_zoominfo_token(token_header, expected_token) -> bool:
    if not token_header or not expected_token:
        return False
    return hmac.compare_digest(token_header.encode(), expected_token.encode())

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

Events

ZoomInfo's Webhooks Implementation Guide defines subscriptions as an object type plus an event type. It does not define event-type strings in the payload.

SubscriptionObject typeEvent type
contact updateContactUpdate
company updateCompanyUpdate
  • Update is the only supported event today. Create and delete events are not offered.
  • Each subscription is either a full payload (default; all fields plus changed attributes) or changed attributes only (changed fields plus default mapping fields such as ID, email, domain).
  • At most 25 profiles per delivery. Only records enriched through the Enterprise API in the past 12 months produce events.
  • Exact payload field names are in the Payload Examples of ZoomInfo's API documentation, not in the implementation guide; check them against a real delivery.

Important Headers

HeaderDescription
x-zoominfo-tokenVerification token generated for the webhook. Compare in constant time

Environment Variables

ZOOMINFO_WEBHOOK_TOKEN=your_zoominfo_verification_token_here   # from "Generate New Token" in ZoomInfo

Local Development

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

Reference Materials


Repository

hookdeck/webhook-skills

v0.1.0 · MIT · Updated Nov 2, 2026

View on GitHub →