Agent skill

Hubspot Webhook Handlers

by jeremylongshore in jeremylongshore/tons-of-skills-marketplace

Build and harden HubSpot v3 webhook handlers that survive production: HMAC-SHA256 signature verification, Redis SET NX deduplication, async batch processing with immediate 200 ACK, dead-letter…

MITAuto-check passedBackend & APIs

Install Hubspot Webhook Handlers

skills CLI
$ npx skills add jeremylongshore/tons-of-skills-marketplace --skill hubspot-webhook-handlers -a claude-code

Project install by default; add -g for ~/.claude/skills/.

GitHub CLI
$ gh skill install jeremylongshore/tons-of-skills-marketplace hubspot-webhook-handlers --agent claude-code

Project scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).

Manual copy
$ git clone --depth 1 https://github.com/jeremylongshore/tons-of-skills-marketplace.git skills-src && mkdir -p .claude/skills && cp -r skills-src/skills/.curated/hubspot-webhook-handlers .claude/skills/hubspot-webhook-handlers && rm -rf skills-src

Use ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.

Claude Code skills documentation · loads skills from .claude/skills/

Facts

Skill name
hubspot-webhook-handlers
GitHub stars
2.8k
Token cost
~5.3k tokens
SKILL.md length
1,130 words
Files
3 (incl. references)
Skills in repo
3,342
Repo updated
First seen
Licence
MIT

At a glance

Build and harden HubSpot v3 webhook handlers that survive production: HMAC-SHA256 signature verification, Redis SET NX deduplication, async batch processing with immediate 200 ACK, dead-letter…

  • Works in 6 steps: Signature verification (neutralizes… → Redis SET NX deduplication (neutralizes… → Immediate ACK with async batch… → …
  • Implementing HubSpot webhooks for the first time
  • SKILL.md covers Overview, Prerequisites, Instructions and Error Handling, plus 3 more sections
  • Calls curl, redis-cli and jq; reaches api.hubapi.com; needs HUBSPOT_CLIENT_SECRET and HUBSPOT_ACCESS_TOKEN

What it does

Hubspot Webhook Handlers is an agent skill from jeremylongshore/tons-of-skills-marketplace. Build and harden HubSpot v3 webhook handlers that survive production: HMAC-SHA256 signature verification, Redis SET NX deduplication, async batch processing with immediate 200 ACK, dead-letter queuing for permanent failures, and event-ordering guards for property-change streams. Use when implementing HubSpot webhooks for the first time, hardening an existing handler against retry storms or duplicate processing, debugging signature verification failures, or designing a reliable event pipeline for contact, company…

Its SKILL.md is about 5.3k tokens, which your agent loads only when the skill is triggered. The skill folder holds 3 other files, including reference files (for example `references/API_REFERENCE.md` and `references/implementation-guide.md`). Compatibility notes: Designed for Claude Code

It sits in Backend & APIs, covering Webhooks and CRM management. It works with HubSpot and Redis. The repository describes itself as: Model-agnostic agent-skills platform with a harness-free canonical layer, verified adapters, and the ccpi package manager. Explore at tonsofskills.com. The licence is MIT.

When your agent uses it

  • Implementing HubSpot webhooks for the first time
  • Hardening an existing handler against retry storms
  • Duplicate processing
  • Debugging signature verification failures

Example prompts

  • “hubspot webhook”
  • “hubspot signature verification”
  • “hubspot webhook dedup”
  • “/hubspot-webhook-handlers”

Requirements

  • Python 3
  • Node.js
  • A credential in HUBSPOT_CLIENT_SECRET
  • Compatibility (from SKILL.md): Designed for Claude Code
  • Pre-approved tools (allowed-tools): Read, Write, Edit, Bash(curl:*), Bash(jq:*), Bash(python3:*), Grep

Workflow steps

6 steps, taken from the step headings in SKILL.md.

  1. Signature verification (neutralizes spoofing and proxy bypass)
  2. Redis SET NX deduplication (neutralizes duplicate delivery)
  3. Immediate ACK with async batch processing (neutralizes retry storms)
  4. Dead-letter queue for permanent failures (neutralizes silent event loss)
  5. Property change ordering guard (neutralizes reversed updates)
  6. List-membership subscription (neutralizes scope mismatch)

What it can do on your machine

Read from SKILL.md and the folder at commit cfae287. It shows what the files ask for, not the result of running them.

  • Tool permissions

    Pre-approves these tools, so the agent can use them without asking each time:

    • Read
    • Write
    • Edit
    • Bash(curl:*)
    • Bash(jq:*)
    • Bash(python3:*)
    • Grep

    From allowed-tools in the SKILL.md frontmatter.

  • Runs code

    Shell commands in SKILL.md call:

    • curl
    • redis-cli
    • jq
    • openssl
    • python3
    • node

    From the folder's file list and the shell code blocks in SKILL.md.

  • Network

    Hosts in commands or code, which the agent is likely to contact:

    • api.hubapi.com

    Also links to:

    • developers.hubspot.com
    • docs.bullmq.io
    • redis.io

    From URLs in SKILL.md, links to its own repository left out.

  • Credentials

    Names these keys or tokens, usually read from environment variables:

    • HUBSPOT_CLIENT_SECRET
    • HUBSPOT_ACCESS_TOKEN
    • CLIENT_SECRET

    From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.

  • Compatibility

    Designed for Claude Code

    From compatibility in the SKILL.md frontmatter.

Context cost

Hubspot Webhook Handlers loads about 5.3k tokens when it runs, and up to ~13k if it reads all its reference files. Until then it costs about 203 tokens; SKILL.md has 1,130 words of instructions outside code blocks.

Always · name and description, kept in context so the agent knows when to use it
~203
When it runs · the whole SKILL.md, loaded when a task matches
~5.3k
With references · SKILL.md plus every file in references/, read only if the agent opens them
~13k

Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.

Safety

Auto-check passed

The automated check found no risky patterns in SKILL.md.

Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.

SKILL.md

The full file from jeremylongshore/tons-of-skills-marketplace at commit cfae287, republished under its MIT licence (© jeremylongshore). 1,130 words, ~5,324 tokens.

Download SKILL.mdSave it as .claude/skills/hubspot-webhook-handlers/SKILL.md (or your agent's skills folder). This skill also uses 2 other files; get the full folder from GitHub.
name
hubspot-webhook-handlers
description
Build and harden HubSpot v3 webhook handlers that survive production: HMAC-SHA256 signature verification, Redis SET NX deduplication, async batch processing with immediate 200 ACK, dead-letter queuing for permanent failures, and event-ordering guards for property-change streams. Use when implementing HubSpot webhooks for the first time, hardening an existing handler against retry storms or duplicate processing, debugging signature verification failures, or designing a reliable event pipeline for contact, company, or deal change events. Trigger with "hubspot webhook", "hubspot signature verification", "hubspot webhook dedup", "hubspot webhook retry storm", "hubspot event handler", "hubspot property change webhook", "hubspot list membership webhook", "hubspot dead letter queue".
allowed-tools
Read, Write, Edit, Bash(curl:*), Bash(jq:*), Bash(python3:*), Grep
compatibility
Designed for Claude Code
version
2.9.0
license
MIT
author
Jeremy Longshore <jeremy@intentsolutions.io>
tags
hubspot, webhooks, event-driven, integration-engineering

HubSpot Webhook Handlers

Overview

Receive and process HubSpot webhook events reliably at production scale. This is not a walkthrough for getting your first event — it is the handler code your integration runs when HubSpot delivers 100 events in a single payload at 2am, when a misconfigured proxy silently strips your signature header, when a 3-day outage causes events to arrive in a burst after recovery, and when a rapid sequence of property updates arrives reversed because HubSpot sends in delivery order rather than chronological order.

The six production failures this skill prevents:

  1. Signature verification bypass — skipping or misconfiguring the HMAC-SHA256 check on X-HubSpot-Signature-v3 allows any party to send spoofed webhook payloads to your endpoint. One misconfigured load balancer or proxy that strips the X-HubSpot-Signature-v3 header silently disables all security — your handler returns 200 to unauthenticated requests without knowing it.
  2. Duplicate delivery — HubSpot retries unacknowledged webhooks (non-200 response or timeout) for up to 3 days with exponential backoff. If your handler crashes after processing but before responding, the same contact-update event creates duplicate CRM mutations downstream. Redis SET NX on the event ID is the reliable guard.
  3. No replay API — events are permanently lost after the 3-day retry window — if your handler is down for more than 3 days, HubSpot drops those events permanently. There is no replay endpoint. Dead-letter queues and recovery runbooks are your only mitigation.
  4. Batch event explosion — a single webhook delivery contains up to 100 events. Synchronous processing of all 100 within the HTTP request context times out (HubSpot timeout: 5 seconds) and returns 5xx, which triggers a retry storm. The correct pattern is to ACK immediately with 200, enqueue the batch, and process asynchronously.
  5. Property change ordering — HubSpot sends contact.propertyChange events in delivery order, not chronological order. A fast property update followed by a slow one can arrive reversed: your handler sees the newer value first, then overwrites it with the older value. Sequence guards on occurredAt are required.
  6. List-membership scope mismatch — subscribing to contact.propertyChange for lifecyclestage does not automatically deliver list-membership changes. Those require a separate subscription to the list-membership event type and a separate oauth scope or crm.lists.read scope on the app.

Prerequisites

  • Node.js 18+ (TypeScript examples) or Python 3.10+
  • Express 4.x (or any HTTP server that can expose a raw body buffer for HMAC verification)
  • Redis 6+ (for SET NX deduplication)
  • A message queue: BullMQ (Redis-backed), RabbitMQ, or SQS (for async batch processing)
  • HubSpot app client secret (Settings → App → Client Secret) — not the access token
  • HUBSPOT_CLIENT_SECRET environment variable set in your runtime
  • For list-membership events: crm.lists.read scope granted to your app

Instructions

Build in this order. Each section neutralizes one production failure mode.

1. Signature verification (neutralizes spoofing and proxy bypass)

HubSpot v3 signatures use HMAC-SHA256 over the concatenation of your client secret, HTTP method, full request URI (including query string), raw request body, and the timestamp from X-HubSpot-Request-Timestamp. You must compute this over the raw body bytes, not a parsed JSON string. Any body middleware that re-serializes JSON will produce a signature mismatch.

The full algorithm:

HMAC-SHA256(
  clientSecret,
  httpMethod + requestUri + rawBody + timestamp
)

The resulting hex digest must match the value in X-HubSpot-Signature-v3.

Timestamp tolerance: reject any request where abs(now - X-HubSpot-Request-Timestamp) > 300 seconds (5 minutes). This prevents replay attacks.

typescript
import { createHmac } from "crypto";
import type { Request, Response, NextFunction } from "express";

const SIGNATURE_TOLERANCE_MS = 5 * 60 * 1000; // 5 minutes

export function verifyHubSpotSignature(
  req: Request,
  res: Response,
  next: NextFunction,
): void {
  const signature = req.headers["x-hubspot-signature-v3"] as string | undefined;
  const timestamp = req.headers["x-hubspot-request-timestamp"] as string | undefined;

  // Reject if either header is missing — do NOT silently pass
  if (!signature || !timestamp) {
    res.status(403).json({
      error: "missing_signature",
      detail: "X-HubSpot-Signature-v3 or X-HubSpot-Request-Timestamp header absent",
    });
    return;
  }

  // Reject stale requests — prevents replay attacks
  const requestAge = Math.abs(Date.now() - parseInt(timestamp, 10));
  if (requestAge > SIGNATURE_TOLERANCE_MS) {
    res.status(403).json({
      error: "timestamp_out_of_window",
      detail: `Request is ${Math.round(requestAge / 1000)}s old; max is 300s`,
    });
    return;
  }

  // Build the signature input string
  // rawBody must be set by express.raw() middleware — NOT express.json()
  const rawBody: Buffer = (req as any).rawBody;
  if (!rawBody) {
    console.error("rawBody not available — check express.raw() middleware ordering");
    res.status(500).json({ error: "misconfigured_middleware" });
    return;
  }

  const method = req.method.toUpperCase();
  // Full URI including query string
  const uri = `${req.protocol}://${req.get("host")}${req.originalUrl}`;
  const signingInput = `${method}${uri}${rawBody.toString("utf8")}${timestamp}`;

  const expected = createHmac("sha256", process.env.HUBSPOT_CLIENT_SECRET!)
    .update(signingInput, "utf8")
    .digest("hex");

  // Constant-time comparison to prevent timing attacks
  const receivedBuf = Buffer.from(signature, "hex");
  const expectedBuf = Buffer.from(expected, "hex");

  if (
    receivedBuf.length !== expectedBuf.length ||
    !crypto.timingSafeEqual(receivedBuf, expectedBuf)
  ) {
    console.warn("HubSpot signature mismatch", {
      expected: expected.slice(0, 8) + "...",
      received: signature.slice(0, 8) + "...",
      uri,
      timestamp,
    });
    res.status(403).json({ error: "invalid_signature" });
    return;
  }

  // Attach parsed body for the route handler
  (req as any).hubspotEvents = JSON.parse(rawBody.toString("utf8"));
  next();
}

Critical: configure Express to capture the raw body. Body middleware that calls JSON.stringify(JSON.parse(...)) re-serializes the body and breaks signature verification:

typescript
import express from "express";

const app = express();

// Use raw() for the webhook route ONLY — not json()
app.use(
  "/webhooks/hubspot",
  express.raw({ type: "application/json", limit: "1mb" }),
  (req, _res, next) => {
    // Preserve the raw buffer before any parsing
    (req as any).rawBody = req.body as Buffer;
    next();
  },
);
2. Redis SET NX deduplication (neutralizes duplicate delivery)

HubSpot guarantees at-least-once delivery. Every event object carries a unique eventId. Use Redis SET NX (set if not exists) with a 24-hour TTL as an idempotency gate. Process the event only if the SET NX succeeds; skip it if the key already exists.

typescript
import type { Redis } from "ioredis";

const DEDUP_TTL_SECONDS = 86_400; // 24 hours — HubSpot retries for up to 3 days

async function isNewEvent(redis: Redis, eventId: number): Promise<boolean> {
  const key = `hubspot:event:${eventId}`;
  // SET key 1 EX 86400 NX — returns "OK" if set, null if already exists
  const result = await redis.set(key, "1", "EX", DEDUP_TTL_SECONDS, "NX");
  return result === "OK";
}

async function processEventIfNew(
  redis: Redis,
  event: HubSpotEvent,
  handler: (event: HubSpotEvent) => Promise<void>,
): Promise<void> {
  const isNew = await isNewEvent(redis, event.eventId);
  if (!isNew) {
    console.debug("Skipping duplicate event", { eventId: event.eventId, type: event.subscriptionType });
    return;
  }

  try {
    await handler(event);
  } catch (err) {
    // If processing fails, delete the dedup key so retries can reprocess
    // Only do this for transient failures — not for permanent business errors
    await redis.del(`hubspot:event:${event.eventId}`);
    throw err;
  }
}
3. Immediate ACK with async batch processing (neutralizes retry storms)

HubSpot expects a 200 response within 5 seconds. For a batch of 100 events, synchronous processing will exceed this window under any real load. The correct pattern is to return 200 immediately, enqueue the batch, and process in a background worker.

typescript
import { Queue } from "bullmq";
import type { Request, Response } from "express";

const eventQueue = new Queue("hubspot-events", {
  connection: { host: process.env.REDIS_HOST, port: parseInt(process.env.REDIS_PORT ?? "6379") },
  defaultJobOptions: {
    attempts: 5,
    backoff: { type: "exponential", delay: 2_000 },
    removeOnComplete: { count: 1000 },
    removeOnFail: false, // keep failed jobs for DLQ inspection
  },
});

export async function webhookHandler(req: Request, res: Response): Promise<void> {
  const events: HubSpotEvent[] = (req as any).hubspotEvents;

  if (!Array.isArray(events) || events.length === 0) {
    res.status(200).json({ accepted: 0 });
    return;
  }

  // ACK immediately — do NOT await processing
  res.status(200).json({ accepted: events.length });

  // Enqueue each event as an individual job
  // Individual jobs allow per-event retry, dedup, and DLQ routing
  await Promise.all(
    events.map((event) =>
      eventQueue.add(event.subscriptionType, event, {
        jobId: `hubspot-${event.eventId}`, // deduplicate at queue level too
      }),
    ),
  );
}
Show full SKILL.md (472 more words)Show less
4. Dead-letter queue for permanent failures (neutralizes silent event loss)

Events that exhaust all retry attempts must land in a dead-letter structure where they can be inspected, replayed, or alerted on. BullMQ's failed job store provides this out of the box, but you need explicit monitoring.

typescript
import { Worker, type Job } from "bullmq";

// Dead-letter handler — called when a job exhausts all attempts
async function onJobFailed(job: Job | undefined, err: Error): Promise<void> {
  if (!job) return;
  if (job.attemptsMade < (job.opts.attempts ?? 1)) return; // still has retries remaining

  // This job is permanently dead — route to DLQ
  const dlqKey = `hubspot:dlq:${job.name}`;
  await redis.lpush(
    dlqKey,
    JSON.stringify({
      jobId: job.id,
      eventId: job.data.eventId,
      portalId: job.data.portalId,
      objectId: job.data.objectId,
      subscriptionType: job.data.subscriptionType,
      occurredAt: job.data.occurredAt,
      failedAt: Date.now(),
      error: err.message,
      attempts: job.attemptsMade,
    }),
  );

  // Alert — use your notification channel
  console.error("HubSpot event permanently failed", {
    jobId: job.id,
    eventId: job.data.eventId,
    type: job.data.subscriptionType,
    error: err.message,
  });
}

const worker = new Worker(
  "hubspot-events",
  async (job) => {
    await processEventIfNew(redis, job.data, routeEvent);
  },
  {
    connection: { host: process.env.REDIS_HOST, port: parseInt(process.env.REDIS_PORT ?? "6379") },
    concurrency: 10, // process up to 10 events in parallel
  },
);

worker.on("failed", onJobFailed);

DLQ replay pattern — when your handler is recovered and you need to reprocess failed events:

typescript
async function replayDeadLetterQueue(
  redis: Redis,
  subscriptionType: string,
  limit = 100,
): Promise<number> {
  const dlqKey = `hubspot:dlq:${subscriptionType}`;
  let replayed = 0;

  for (let i = 0; i < limit; i++) {
    const raw = await redis.rpop(dlqKey);
    if (!raw) break;

    const dead = JSON.parse(raw) as { eventId: number; [key: string]: unknown };

    // Delete the dedup key so the replayed event is treated as new
    await redis.del(`hubspot:event:${dead.eventId}`);

    await eventQueue.add(subscriptionType, dead, {
      jobId: `hubspot-replay-${dead.eventId}-${Date.now()}`,
    });

    replayed++;
  }

  console.info(`Replayed ${replayed} dead-letter events for ${subscriptionType}`);
  return replayed;
}
5. Property change ordering guard (neutralizes reversed updates)

HubSpot delivers contact.propertyChange events in delivery order, not chronological order. When a contact's property changes twice in rapid succession, the second change can arrive before the first. If you apply them in delivery order, you overwrite a newer value with an older one.

Guard every write operation with an occurredAt comparison:

typescript
type PropertyVersion = { value: string; occurredAt: number };

async function applyPropertyChange(
  redis: Redis,
  objectId: number,
  propertyName: string,
  newValue: string,
  occurredAt: number,
): Promise<void> {
  const versionKey = `hubspot:prop:${objectId}:${propertyName}`;

  // Read the last-applied version
  const existingRaw = await redis.get(versionKey);
  const existing: PropertyVersion | null = existingRaw ? JSON.parse(existingRaw) : null;

  if (existing && existing.occurredAt >= occurredAt) {
    console.debug("Ignoring stale property change", {
      objectId,
      propertyName,
      existingTimestamp: existing.occurredAt,
      incomingTimestamp: occurredAt,
    });
    return;
  }

  // Safe to apply — this is the newest value seen for this property
  await redis.set(
    versionKey,
    JSON.stringify({ value: newValue, occurredAt }),
    "EX",
    7 * 86_400, // 7-day TTL — keep long enough to guard burst redeliveries
  );

  // Now apply the change to your downstream system
  await updateContactProperty(objectId, propertyName, newValue);
}
6. List-membership subscription (neutralizes scope mismatch)

contact.propertyChange for lifecyclestage does NOT deliver list-membership changes. These require a separate subscription. Use the webhook subscription API to register:

bash
# Register a contact.propertyChange subscription
curl -X POST "https://api.hubapi.com/webhooks/v3/{appId}/subscriptions" \
  -H "Authorization: Bearer $HUBSPOT_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "eventType": "contact.propertyChange",
    "propertyName": "lifecyclestage",
    "active": true
  }'

# Register a list-membership subscription (separate subscription required)
# Note: HubSpot does not expose a direct list.membershipChange event type.
# Use contact.propertyChange for hs_all_contact_vids or workflow enrollment events,
# or poll GET /crm/v3/lists/{listId}/memberships for list-driven automation.

Verify active subscriptions before debugging delivery:

bash
curl -s "https://api.hubapi.com/webhooks/v3/{appId}/subscriptions" \
  -H "Authorization: Bearer $HUBSPOT_ACCESS_TOKEN" | jq '.results[] | {id, eventType, propertyName, active}'

Error Handling

HTTP StatusErrorRoot CauseAction
403 FORBIDDENmissing_signatureX-HubSpot-Signature-v3 or X-HubSpot-Request-Timestamp header absentCheck proxy/load balancer header passthrough; add header logging at the edge
403 FORBIDDENinvalid_signatureHMAC digest does not match; raw body was re-serializedVerify express.raw() middleware is used (not express.json()); confirm HUBSPOT_CLIENT_SECRET is the app client secret, not the access token
403 FORBIDDENtimestamp_out_of_windowRequest timestamp is >5 minutes from server timeCheck server NTP sync; clock skew on container or serverless host
500 INTERNAL_SERVER_ERRORmisconfigured_middlewarerawBody buffer not attached to requestexpress.raw() must run before signature middleware on the webhook route
200 OK (but no events processed)Duplicate suppressedRedis SET NX returned null; event already processedExpected — dedup is working
200 OK (but DLQ growing)Permanent processing failureDownstream system error; event exhausted retriesInspect DLQ via redis LRANGE hubspot:dlq:<type> 0 -1; fix downstream, then replay
429 TOO_MANY_REQUESTSRate limit on subscription APIToo many subscription management callsBack off; subscription changes are rare — cache the subscription list
No delivery receivedSubscription inactive or app not installedactive: false on subscription or app revoked by portal adminRun GET /webhooks/v3/{appId}/subscriptions and verify active: true

Examples

Minimal end-to-end wiring (Express)
typescript
import express from "express";
import { createClient } from "ioredis";
import { verifyHubSpotSignature } from "./middleware/signature";
import { webhookHandler } from "./handlers/webhook";

const app = express();
const redis = createClient({ host: process.env.REDIS_HOST });

// IMPORTANT: raw body capture before JSON parsing
app.use(
  "/webhooks/hubspot",
  express.raw({ type: "application/json", limit: "1mb" }),
  (req, _res, next) => {
    (req as any).rawBody = req.body as Buffer;
    next();
  },
  verifyHubSpotSignature,
  webhookHandler,
);

app.listen(3000, () => console.log("Webhook listener on :3000"));
Test signature verification locally
bash
# Compute the expected signature for a test payload
CLIENT_SECRET="your-client-secret"
METHOD="POST"
URI="https://your-host.example.com/webhooks/hubspot"
BODY='[{"eventId":1,"subscriptionType":"contact.propertyChange"}]'
TIMESTAMP="$(date +%s%3N)"  # milliseconds

EXPECTED=$(echo -n "${METHOD}${URI}${BODY}${TIMESTAMP}" | \
  openssl dgst -sha256 -hmac "$CLIENT_SECRET" | awk '{print $2}')

curl -X POST "$URI" \
  -H "Content-Type: application/json" \
  -H "X-HubSpot-Signature-v3: $EXPECTED" \
  -H "X-HubSpot-Request-Timestamp: $TIMESTAMP" \
  -d "$BODY"
Inspect the dead-letter queue
bash
# View all entries in the DLQ for contact.propertyChange
redis-cli LRANGE hubspot:dlq:contact.propertyChange 0 -1 | python3 -m json.tool

# Count DLQ depth per event type
redis-cli KEYS "hubspot:dlq:*" | xargs -I{} sh -c 'echo "{}: $(redis-cli LLEN {})"'

# Replay up to 50 failed contact.deletion events
node -e "
const { replayDeadLetterQueue } = require('./dist/dlq');
replayDeadLetterQueue(redis, 'contact.deletion', 50).then(console.log);
"
Register subscriptions for all CRM object types
bash
APP_ID="your-app-id"
TOKEN="$HUBSPOT_ACCESS_TOKEN"
BASE="https://api.hubapi.com/webhooks/v3/$APP_ID/subscriptions"

for EVENT_TYPE in contact.creation contact.deletion contact.propertyChange \
                  contact.merge company.creation deal.creation deal.propertyChange; do
  curl -s -X POST "$BASE" \
    -H "Authorization: Bearer $TOKEN" \
    -H "Content-Type: application/json" \
    -d "{\"eventType\": \"$EVENT_TYPE\", \"active\": true}" | jq '{id, eventType, active}'
done

Output

  • Express webhook handler with express.raw() body capture and HMAC-SHA256 signature verification
  • Redis SET NX deduplication on eventId with 24-hour TTL
  • Immediate 200 ACK with async BullMQ batch enqueue
  • Dead-letter queue with redis.lpush for exhausted retries and a replay function
  • occurredAt ordering guard for contact.propertyChange events
  • Subscription registration curl commands for all CRM event types
  • Middleware ordering that prevents the re-serialization signature mismatch

Resources

© jeremylongshore, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file

Files

SKILL.md and 2 other files (references) in skills/.curated/hubspot-webhook-handlers of jeremylongshore/tons-of-skills-marketplace.

  • SKILL.md
  • references/API_REFERENCE.md
  • references/implementation-guide.md

Open the folder on GitHubat commit cfae287

Compare with similar skills

Hubspot Webhook Handlers next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.

Hubspot Webhook Handlers compared with similar skills
SkillStarsUsed inTokensAuto-checkLicenceRepo updated
Hubspot Webhook Handlers this skilljeremylongshore/tons-of-skills-marketplace2.8k—~5.3kAutomated safety check: PassMIT
Hubspot Integrationdavila7/claude-code-templates33k4 repos~265Automated safety check: PassMIT
Hubspot Integrationaiskillstore/marketplace4333 repos~5kAutomated safety check: PassNone
Chat SDK Botslobehub/lobehub83k—~1.5kAutomated safety check: PassCustom licence
API GatewayCraftOS-dev/CraftBot3923 repos~7.1kAutomated safety check: PassMIT
Databricks Lakeflow Connectdatabricks/databricks-agent-skills345—~3.8kAutomated safety check: PassCustom licence

Similar skills

  • Hubspot Integration

    davila7/claude-code-templates

    Expert patterns for HubSpot CRM integration including OAuth authentication, CRM objects, associations, batch operations, webhooks, and custom objects.

    33k GitHub starsUsed in 4 repos~265 tokens
    Sales & SupportAuto-check passed
  • Hubspot Integration

    aiskillstore/marketplace

    Expert patterns for HubSpot CRM integration including OAuth authentication, CRM objects, associations, batch operations, webhooks, and custom objects.

    433 GitHub starsUsed in 3 repos~5k tokens
    Sales & SupportAuto-check passed
  • Chat SDK Bots

    lobehub/lobehub

    Builds chat bots once for Slack, Teams, Google Chat, Discord, GitHub and Linear with the Chat SDK, covering event handlers, cards, modals, streaming and state adapters.

    83k GitHub stars~1.5k tokensUpdated today
    Backend & APIsAuto-check passed
  • API Gateway

    CraftOS-dev/CraftBot

    Connect to 100+ APIs (Google Workspace, Microsoft 365, Notion, Slack, Airtable, HubSpot, etc.) with managed OAuth.

    392 GitHub starsUsed in 3 repos~7.1k tokens
    Backend & APIsAuto-check passed
  • Databricks Lakeflow Connect

    databricks/databricks-agent-skills

    Official

    Build managed ingestion pipelines into Databricks using Lakeflow Connect.

    345 GitHub stars~3.8k tokensUpdated yesterday
    Sales & SupportAuto-check passed
  • Idempotency Handling

    secondsky/claude-skills

    Idempotent API operations with idempotency keys, Redis caching, DB constraints.

    227 GitHub stars~1.4k tokensUpdated 13 days ago
    Backend & APIsAuto-check passed

More from jeremylongshore/tons-of-skills-marketplace

All 3,342 skills in this repo
  • Performing Security Code Review

    jeremylongshore/tons-of-skills-marketplace

    Execute this skill enables AI assistant to conduct a security-focused code review using the security-agent plugin.

    2.8k GitHub starsUsed in 2 repos~1.3k tokens
    Auto-check: notes
  • Adapting Transfer Learning Models

    jeremylongshore/tons-of-skills-marketplace

    Build this skill automates the adaptation of pre-trained machine learning models using transfer learning techniques.

    2.8k GitHub stars~1.1k tokensUpdated today
    Auto-check passed
  • Agent Context Loader

    jeremylongshore/tons-of-skills-marketplace

    Execute proactive auto-loading: automatically detects and loads agents.md files.

    2.8k GitHub stars~1.1k tokensUpdated today
    Auto-check passed
  • Aggregating Performance Metrics

    jeremylongshore/tons-of-skills-marketplace

    Aggregate and centralize performance metrics from applications, systems, databases, caches, and services.

    2.8k GitHub stars~1.2k tokensUpdated today
    Auto-check passed
  • Analyzing Capacity Planning

    jeremylongshore/tons-of-skills-marketplace

    Execute this skill enables AI assistant to analyze capacity requirements and plan for future growth.

    2.8k GitHub stars~947 tokensUpdated today
    Auto-check passed
  • Analyzing Database Indexes

    jeremylongshore/tons-of-skills-marketplace

    Process use when you need to work with database indexing. An agent skill from jeremylongshore/tons-of-skills-marketplace.

    2.8k GitHub stars~2k tokensUpdated today
    Auto-check passed

Works with

Questions about Hubspot Webhook Handlers

What does Hubspot Webhook Handlers do?

Build and harden HubSpot v3 webhook handlers that survive production: HMAC-SHA256 signature verification, Redis SET NX deduplication, async batch processing with immediate 200 ACK, dead-letter…. Hubspot Webhook Handlers is an agent skill from jeremylongshore/tons-of-skills-marketplace. Build and harden HubSpot v3 webhook handlers that survive production: HMAC-SHA256 signature verification, Redis SET NX deduplication, async batch processing with immediate 200 ACK, dead-letter queuing for permanent failures, and event-ordering guards for property-change streams.

When should I use Hubspot Webhook Handlers?

Hubspot Webhook Handlers fits situations like: implementing HubSpot webhooks for the first time; hardening an existing handler against retry storms; duplicate processing; debugging signature verification failures.

How do I install Hubspot Webhook Handlers in Claude Code?

Run `npx skills add jeremylongshore/tons-of-skills-marketplace --skill hubspot-webhook-handlers -a claude-code`. Or copy the skill folder (skills/.curated/hubspot-webhook-handlers in jeremylongshore/tons-of-skills-marketplace) into .claude/skills/hubspot-webhook-handlers in your project. Claude Code loads it when a task matches its description.

How do I install Hubspot Webhook Handlers in Codex?

Run `npx skills add jeremylongshore/tons-of-skills-marketplace --skill hubspot-webhook-handlers -a codex`. Or copy the skill folder (skills/.curated/hubspot-webhook-handlers in jeremylongshore/tons-of-skills-marketplace) into .agents/skills/hubspot-webhook-handlers in your project. Codex loads it when a task matches its description.

Can I use Hubspot Webhook Handlers in Cursor, Gemini CLI or GitHub Copilot?

Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add jeremylongshore/tons-of-skills-marketplace --skill hubspot-webhook-handlers -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/hubspot-webhook-handlers, .gemini/skills/hubspot-webhook-handlers, .github/skills/hubspot-webhook-handlers and .opencode/skills/hubspot-webhook-handlers in your project.

What does Hubspot Webhook Handlers need to run?

Going by SKILL.md and its folder, Hubspot Webhook Handlers needs the command-line tools its instructions call (curl, redis-cli, jq, openssl, python3 and node) and credentials named HUBSPOT_CLIENT_SECRET, HUBSPOT_ACCESS_TOKEN and CLIENT_SECRET. Our summary lists: Python 3; Node.js; A credential in HUBSPOT_CLIENT_SECRET. Its frontmatter pre-approves these tools: Read, Write, Edit, Bash(curl:*), Bash(jq:*), Bash(python3:*), Grep. Compatibility (from SKILL.md): Designed for Claude Code.

Does Hubspot Webhook Handlers access the network?

SKILL.md names 4 domains. In commands or code: api.hubapi.com; the agent is likely to contact it when it follows the instructions. As links in the text: developers.hubspot.com, docs.bullmq.io and redis.io. This is read from the text; nothing was executed.

Is Hubspot Webhook Handlers safe to install?

Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.

What licence does Hubspot Webhook Handlers use?

Hubspot Webhook Handlers is published under the MIT licence (declared in SKILL.md). It allows redistribution, so the full SKILL.md is shown on this page.

How many tokens does Hubspot Webhook Handlers use?

About 5.3k tokens (SKILL.md is roughly 21k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full. Its references folder adds about 7.9k tokens, read only when the agent opens those files.

What are the alternatives to Hubspot Webhook Handlers?

Skills that share tags, products or a category with Hubspot Webhook Handlers: Hubspot Integration (davila7/claude-code-templates, 33k stars), Hubspot Integration (aiskillstore/marketplace, 433 stars), Chat SDK Bots (lobehub/lobehub, 83k stars) and API Gateway (CraftOS-dev/CraftBot, 392 stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.

Who maintains Hubspot Webhook Handlers?

jeremylongshore (a GitHub user) maintains it in jeremylongshore/tons-of-skills-marketplace, which has 2,827 GitHub stars. The repository holds 3,342 skills in this directory. The repository was last updated on October 10, 2026.

Source: jeremylongshore/tons-of-skills-marketplace on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.