customerio-webhooks-events
Provides instructions for processing Customer.io delivery events, bounce management, and real-time webhook integration.
Install
mkdir -p .claude/skills/customerio-webhooks-events && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/8968" && unzip -o skill.zip -d .claude/skills/customerio-webhooks-events && rm skill.zipInstalls to .claude/skills/customerio-webhooks-events
Activation
This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.
Implement Customer.io webhook and reporting event handling.Key capabilities
- →Receive real-time delivery events
- →Verify HMAC-SHA256 signatures
- →Parse and route webhook event types
- →Deduplicate events using event IDs
- →Stream events to data warehouses
How it works
The skill implements an Express route that verifies the HMAC signature of incoming webhooks before parsing the payload. It processes events asynchronously to maintain responsiveness and supports deduplication.
Inputs & outputs
When to use customerio-webhooks-events
- →Track email delivery status
- →Process bounce and complaint events
- →Stream click/open events to a warehouse
- →Handle webhook callbacks reliably
About this skill
Customer.io Webhooks & Events
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
// 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
// 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
// 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 "delivered":
case "sent":
await handleDelivery(event);
break;
default:
console.log(`CIO webhook: ${event.metric} for ${event.customer_id}`);
}
}
async function handleBounce(event: CioWebhookEvent): Promise<void> {
console.warn(`BOUNCE: ${event.email_address} (user: ${event.customer_id})`);
// Update user profile with bounce info
await cio.identify(event.customer_id, {
email_bounced: true,
email_bounced_at: event.timestamp,
last_bounce_delivery_id: event.delivery_id,
});
// Suppress after hard bounce to protect sender reputation
await cio.suppress(event.customer_id);
}
async function handleSpamComplaint(event: CioWebhookEvent): Promise<void> {
console.error(`SPAM COMPLAINT: ${event.email_address}`);
// Suppress immediately — spam complaints damage sender reputation
await cio.suppress(event.customer_id);
// Record for compliance
await cio.identify(event.customer_id, {
spam_complaint: true,
spam_complaint_at: event.timestamp,
});
}
async function handleUnsubscribe(event: CioWebhookEvent): Promise<void> {
await cio.identify(event.customer_id, {
unsubscribed: true,
unsubscribed_at: event.timestamp,
});
}
async function handleEngagement(event: CioWebhookEvent): Promise<void> {
await cio.identify(event.customer_id, {
last_email_engaged_at: event.timestamp,
last_email_metric: event.metric,
});
}
async function handleDelivery(event: CioWebhookEvent): Promise<void> {
// Log for analytics — lightweight processing
console.log(
`${event.metric}: ${event.delivery_id} to ${event.customer_id}`
);
}
Step 4: Express Server Setup
// server.ts
import express from "express";
import webhookRouter from "./routes/webhooks/customerio";
const app = express();
// Raw body parser for webhook signature verification
// MUST be before any JSON body parser
app.use(
"/webhooks/customerio",
express.raw({ type: "application/json" }),
(req, _res, next) => {
(req as any).rawBody = req.body;
req.body = JSON.parse(req.body.toString());
next();
}
);
// JSON parser for all other routes
app.use(express.json());
app.use(webhookRouter);
app.listen(3000, () => console.log("Webhook server on :3000"));
Step 5: Stream to Data Warehouse (BigQuery)
// services/customerio-warehouse.ts
import { BigQuery } from "@google-cloud/bigquery";
const bq = new BigQuery();
const dataset = bq.dataset("messaging");
const table = dataset.table("customerio_events");
async function streamToWarehouse(event: CioWebhookEvent): Promise<void> {
await table.insert({
event_id: event.event_id,
metric: event.metric,
customer_id: event.customer_id,
email_address: event.email_address,
delivery_id: event.delivery_id,
campaign_id: event.campaign_id,
template_id: event.template_id,
href: event.href,
timestamp: new Date(event.timestamp * 1000).toISOString(),
received_at: new Date().toISOString(),
});
}
Dashboard Configuration
- Go to Data & Integrations > Integrations > Reporting Webhooks
- Click Add Reporting Webhook
- Enter your endpoint URL:
https://your-domain.com/webhooks/customerio - Select events to receive (recommended: all for analytics)
- Copy the webhook signing key to
CUSTOMERIO_WEBHOOK_SECRET
Error Handling
| Issue | Solution |
|---|---|
| Invalid signature | Verify webhook secret matches dashboard value |
| Duplicate events | Deduplicate by event_id (use Redis SET with TTL in production) |
| Slow processing | Return 200 immediately, process async |
| Missing events | Check endpoint is publicly accessible, verify IP allowlist |
Resources
Next Steps
After webhook setup, proceed to customerio-performance-tuning for optimization.
Prerequisites
Limitations
- →Requires public HTTPS endpoint
- →Requires raw body parsing for signature verification
How it compares
It provides a secure, verified signature-checking mechanism and asynchronous processing pattern compared to a simple endpoint listener.
Compared to similar skills
customerio-webhooks-events side by side with the closest alternatives in the catalog.
| Skill | Installs | Updated | Safety | Difficulty |
|---|---|---|---|---|
| customerio-webhooks-events (this skill) | 0 | 27d | Review | Intermediate |
| telegram-bot-builder | 106 | 6mo | Review | Intermediate |
| customerio-hello-world | 1 | 27d | Review | Beginner |
| customerio-primary-workflow | 1 | 27d | Review | Intermediate |
Try saying
Example prompts that trigger this skill in your AI assistant.
More by jeremylongshore
View all by jeremylongshore →You might also like
telegram-bot-builder
davila7
Expert in building Telegram bots that solve real problems - from simple automation to complex AI-powered bots. Covers bot architecture, the Telegram Bot API, user experience, monetization strategies, and scaling bots to thousands of users. Use when: telegram bot, bot api, telegram automation, chat bot telegram, tg bot.
customerio-hello-world
jeremylongshore
Create a minimal working Customer.io example. Use when learning Customer.io basics, testing SDK setup, or creating your first messaging integration. Trigger with phrases like "customer.io hello world", "first customer.io message", "test customer.io", "customer.io example".
customerio-primary-workflow
jeremylongshore
Execute Customer.io primary messaging workflow. Use when setting up email campaigns, push notifications, SMS messaging, or in-app message workflows. Trigger with phrases like "customer.io campaign", "customer.io workflow", "customer.io email automation", "customer.io messaging".
FCM Job Queue Integration
nexonic-technologies
Instructions and standards for managing asynchronous FCM Push Notifications via the JobQueue system.
telegram-mini-app
davila7
Expert in building Telegram Mini Apps (TWA) - web apps that run inside Telegram with native-like experience. Covers the TON ecosystem, Telegram Web App API, payments, user authentication, and building viral mini apps that monetize. Use when: telegram mini app, TWA, telegram web app, TON app, mini app.
stripe-integration
wshobson
Implement Stripe payment processing for robust, PCI-compliant payment flows including checkout, subscriptions, and webhooks. Use when integrating Stripe payments, building subscription systems, or implementing secure checkout flows.