hubspot-webhook-handlers
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".
What this skill does
# 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);
} catcRelated in Backend & APIs
jfrog
IncludedInteract with the JFrog Platform via the JFrog CLI and REST/GraphQL APIs. Use this skill when the user wants to manage Artifactory repositories, upload or download artifacts, manage builds, configure permissions, manage users and groups, work with access tokens, configure JFrog CLI servers, search artifacts, manage properties, set up replication, manage JFrog Projects, run security audits or scans, look up CVE details, query exposures scan results from JFrog Advanced Security, manage release bundles and lifecycle operations, aggregate or export platform data, or perform any JFrog Platform administration task. Also use when the user mentions jf, jfrog, artifactory, xray, distribution, evidence, apptrust, onemodel, graphql, workers, mission control, curation, advanced security, exposures, or any JFrog product name.
cupynumeric-migration-readiness
IncludedPre-migration readiness assessor for porting NumPy to cuPyNumeric. Use BEFORE substantial porting work begins when the user asks whether code will scale on GPU, whether they should migrate to cuPyNumeric, which NumPy patterns transfer cleanly, what must be refactored before porting, or mentions pre-port assessment, scaling analysis, or refactor planning. Inspect the user's source code, look up NumPy usage, cross-reference the cuPyNumeric API support manifest, and distinguish distributed-scaling-friendly patterns from blockers such as unsupported APIs, scalar synchronization, host round-trips, Python/object-heavy control flow, shape/data-dependent branching, and in-place mutation hazards. Produce a verdict of READY, LIGHT REFACTOR, SIGNIFICANT REFACTOR, or NOT RECOMMENDED, with concrete refactor pointers.
alibabacloud-data-agent-skill
IncludedInvoke Alibaba Cloud Apsara Data Agent for Analytics via CLI to perform natural language-driven data analysis on enterprise databases. Data Agent for Analytics is an intelligent data analysis agent developed by Alibaba Cloud Database team for enterprise users. It automatically completes requirement analysis, data understanding, analysis insights, and report generation based on natural language descriptions. This tool supports: discovering data resources (instances/databases/tables) managed in DMS, initiating query or deep analysis sessions, real-time progress tracking, and retrieving analysis conclusions and generated reports. Use this Skill when users need to query databases, analyze data trends, generate data reports, ask questions in natural language, or mention "Data Agent", "data analysis", "database query", "SQL analysis", "data insights".
token-optimizer
IncludedReduce OpenClaw token usage and API costs through smart model routing, heartbeat optimization, budget tracking, and native 2026.2.15 features (session pruning, bootstrap size limits, cache TTL alignment). Use when token costs are high, API rate limits are being hit, or hosting multiple agents at scale. The 4 executable scripts (context_optimizer, model_router, heartbeat_optimizer, token_tracker) are local-only — no network requests, no subprocess calls, no system modifications. Reference files (PROVIDERS.md, config-patches.json) document optional multi-provider strategies that require external API keys and network access if you choose to use them. See SECURITY.md for full breakdown.
resend-cli
IncludedUse this skill when the task is specifically about operating Resend from an AI agent, terminal session, or CI job via the official resend CLI: installing/authenticating the CLI, sending/listing/updating/cancelling emails, batch sends, domains and DNS, webhooks and local listeners, inbound receiving, contacts, topics, segments, broadcasts, templates, API keys, profiles, or debugging Resend CLI/API failures. Trigger on mentions of Resend CLI, `resend`, `resend doctor`, `resend emails send`, `resend domains`, `resend webhooks listen`, `resend emails receiving`, or agent-friendly terminal automation.
alibabacloud-odps-maxframe-coding
IncludedUse this skill for MaxFrame SDK development and documentation navigation on Alibaba Cloud MaxCompute (ODPS). Helps answer MaxFrame API, concept, official example, and supported pandas API questions; create data processing programs; read/write MaxCompute tables; debug jobs (remote or local); and build custom DPE runtime images. Trigger when users mention MaxFrame, MaxCompute with MaxFrame, ODPS table processing, DPE runtime, MaxFrame docs/examples, DataFrame/Tensor operations, or GPU runtime setup. Works for both English and Chinese queries about Alibaba Cloud data processing with MaxFrame.