Customerio Webhooks EventsSAFE
Model-agnostic agent-skills platform with a harness-free canonical layer, verified adapters, and the ccpi package manager. Explore at tonsofskills.com.
Overview
Model-agnostic agent-skills platform with a harness-free canonical layer, verified adapters, and the ccpi package manager. Explore at tonsofskills.com.
4f83675ca38aOBSERVED · 2026-10-08Host compatibility
What the documentation claims. We have not run a compatibility test.
| Host | Status | Notes |
|---|---|---|
| claude-code | mentioned |
What it tells the agent
The instruction file, verbatim from the audited commit — this is the text the model reads, and the surface the audit's instruction layer examines. Quoted here so you can judge it without cloning anything.
--- name: customerio-webhooks-events description: 'Implement Customer.io webhook and reporting event handling. Use when processing email delivery events, click/open tracking, bounce handling, or streaming to a data warehouse. Trigger: "customer.io webhook", "customer.io events", "customer.io delivery status", "customer.io bounces", "customer.io open tracking". ' allowed-tools: Read, Write, Edit, Bash(npm:*), Bash(npx:*), Glob, Grep version: 1.14.0 license: MIT author: Jeremy Longshore <[email protected]> tags: - saas - customer-io - webhooks - events - delivery compatibility: Designed for Claude Code --- # Customer.io Webhooks & Events ## Output - A validated, signed, idempotent event/webhook flow with redacted observability and an owned retry policy. - A delivery receipt that records correlation ID, environment, event type, result, and consent-safe recovery decision. ## Examples Verify a webhook signature before parsing payload data, store only the opaque event ID for deduplication, and acknowledge within the provider timeout. For a failed processing attempt, retry with bounded backoff; send terminal failures to a protected queue and replay only after schema and consent checks pass. ## Overview Implement Customer.io reporting webhook handling: receive real-time delivery events (sent, delivered, opened, clicked, bounced, complained, unsubscribed), verify HMAC-SHA256 signatures, process events reliably with queuing, and stream to a data warehouse. ## How Reporting Webhooks Work ``` Customer.io Your Server Data Warehouse ────────── ─────────── ────────────── Email sent → POST /webhooks/cio → Verify signature Email opened → POST /webhooks/cio → Parse event type Link clicked → POST /webhooks/cio → Route to handler → INSERT INTO events Email bounced → POST /webhooks/cio → Suppress user ``` Configure at: Data & Integrations > Integrations > Reporting Webhooks ## Prerequisites - Public HTTPS endpoint for webhook receiver - Webhook signing key from Customer.io dashboard - Express or similar HTTP framework ## Instructions ### Step 1: Define Webhook Event Types ```typescript // types/customerio-webhooks.ts // Customer.io reporting webhook event metrics type CioMetric = | "sent" // Message sent to delivery provider | "delivered" // Delivery provider confirmed receipt | "opened" // Recipient opened the email | "clicked" // Recipient clicked a link | "converted" // Recipient completed a conversion goal | "bounced" // Email bounced (hard or soft) | "spammed" // Recipient marked as spam | "unsubscribed" // Recipient unsubscribed | "dropped" // Message dropped (suppressed, invalid) | "deferred" // Delivery temporarily deferred | "failed"; // Delivery failed interface CioWebhookEvent { // Event metadata event_id: string; metric: CioMetric; timestamp: number; // Unix seconds // Recipient info customer_id: string; email_address?: string; // Message info subject?: string; template_id?: number; campaign_id?: number; broadcast_id?: number; action_id?: number; // Delivery details delivery_id?: string; // Link tracking (for "clicked" events) href?: string; link_id?: number; } ``` ### Step 2: Webhook Handler with Signature Verification ```typescript // routes/webhooks/customerio.ts import { createHmac, timingSafeEqual } from "crypto"; import { Router, Request, Response } from "express"; const WEBHOOK_SECRET = process.env.CUSTOMERIO_WEBHOOK_SECRET!; function verifySignature(rawBody: Buffer, signature: string): boolean { const expected = createHmac("sha256", WEBHOOK_SECRET) .update(rawBody) .digest("hex"); try { return timingSafeEqual( Buffer.from(signature, "utf-8"), Buffer.from(expected, "utf-8") ); } catch { return false; } } const router = Router(); // IMPORTANT: Use raw body parser for this route — JSON parsing breaks signature verification router.post("/webhooks/customerio", (req: Request, res: Response) => { const signature = req.headers["x-cio-signature"] as string; const rawBody = (req as any).rawBody as Buffer; if (!signature || !rawBody) { res.status(401).json({ error: "Missing signature" }); return; } if (!verifySignature(rawBody, signature)) { console.error("CIO webhook: invalid signature"); res.status(401).json({ error: "Invalid signature" }); return; } const event: CioWebhookEvent = JSON.parse(rawBody.toString()); // Respond 200 immediately — process async to avoid timeouts res.sendStatus(200); // Process event asynchronously handleWebhookEvent(event).catch((err) => console.error("Webhook processing failed:", err) ); }); ``` ### Step 3: Event Router and Handlers ```typescript // services/customerio-webhook-handler.ts import { TrackClient, RegionUS } from "customerio-node"; const cio = new TrackClient( process.env.CUSTOMERIO_SITE_ID!, process.env.CUSTOMERIO_TRACK_API_KEY!, { region: RegionUS } ); // Deduplication set (use Redis in production) const processedEvents = new Set<string>(); async function handleWebhookEvent(event: CioWebhookEvent): Promise<void> { // Deduplicate by event_id if (processedEvents.has(event.event_id)) return; processedEvents.add(event.event_id); // Limit in-memory set size if (processedEvents.size > 100_000) { const iterator = processedEvents.values(); for (let i = 0; i < 50_000; i++) iterator.next(); // In production, use Redis with TTL instead } switch (event.metric) { case "bounced": await handleBounce(event); break; case "spammed": await handleSpamComplaint(event); break; case "unsubscribed": await handleUnsubscribe(event); break; case "opened": case "clicked": await handleEngagement(event); break; case "deliver
Trust audit
SAFEgrade B · trust 89/100 Nothing in the source contradicts what it says it does. Grade A is reserved for packages that have also passed the behavioural sandbox.
| Layer | What it checks | Result |
|---|---|---|
| L0 | Provenance & inventory | PASS |
| L1 | Static analysis of the code | PASS |
| L2 | Instruction surface (what it tells the agent) | PASS |
| L3 | Class-specific surface | PASS |
| L4 | Behavioural (sandbox) | SKIPPED |
What the source does
- Filesystem
- none-observed
- Network
- none-observed
- Shell
- none-observed
- Dependencies
- pinned
- Secrets in source
- none-found
Findings (0)
No findings outside the package's declared scope.
Gates applied: no_behavioural_pass.
4f83675ca38afull audit observations/trust-audit/skill/jeremylongshore__customerio-webhooks-events.json · Report an issue / request a re-scanAudit history
Every audit this skill has had.
| Date | Source | Verdict | Grade | Score | Change |
|---|---|---|---|---|---|
| 2026-10-08 | 4f83675ca38a | SAFE | B | 89 | first audit |
Questions
What does the Customerio Webhooks Events skill do?
Model-agnostic agent-skills platform with a harness-free canonical layer, verified adapters, and the ccpi package manager. Explore at tonsofskills.com.
Is Customerio Webhooks Events safe to install?
The audit found nothing in the source that contradicts what it says it does, and graded it B (89/100). Grade A is held back for packages that have also passed a sandboxed behavioural run, which is why a clean skill reads B.
What can Customerio Webhooks Events access on my machine?
The audit observed no filesystem, network or shell use at all in its source.
Which assistants does Customerio Webhooks Events work with?
Its documentation mentions claude-code. That is what the text claims, not a compatibility test we ran.
How current is this page?
The grade is for one exact copy of the source (4f83675ca38a), read on 2026-10-08. The repository is watched, and a new audit runs when it changes — this is the first audit.