| name | customerio-webhooks-events |
| description | Implement Customer.io webhook handling.
Use when processing delivery events, handling callbacks,
or integrating Customer.io event streams.
Trigger with phrases like "customer.io webhook", "customer.io events",
"customer.io callback", "customer.io delivery status".
|
| allowed-tools | Read, Write, Edit, Bash(gh:*), Bash(curl:*) |
| version | 1.0.0 |
| license | MIT |
| author | Jeremy Longshore <jeremy@intentsolutions.io> |
Customer.io Webhooks & Events
Overview
Implement webhook handling for Customer.io events including email delivery, opens, clicks, and bounces.
Prerequisites
- Public endpoint for webhooks
- Webhook signing secret from Customer.io
- Event processing infrastructure
Instructions
Step 1: Webhook Event Types
export type WebhookEventType =
| 'email_sent'
| 'email_delivered'
| 'email_opened'
| 'email_clicked'
| 'email_bounced'
| 'email_complained'
| 'email_unsubscribed'
| 'email_converted'
| 'push_sent'
| 'push_delivered'
| 'push_opened'
| 'push_bounced'
| 'sms_sent'
| 'sms_delivered'
| 'sms_failed'
| 'in_app_opened'
| 'in_app_clicked';
export interface WebhookEvent {
event_id: string;
object_type: 'email' | 'push' | 'sms' | 'in_app';
metric: string;
timestamp: number;
data: {
customer_id: string;
email_address?: string;
campaign_id?: number;
action_id?: number;
broadcast_id?: number;
newsletter_id?: number;
transactional_message_id?: number;
delivery_id: string;
subject?: string;
link?: string;
recipient?: string;
identifiers?: {
id?: string;
email?: string;
};
};
}
export interface WebhookPayload {
events: WebhookEvent[];
}
Step 2: Webhook Handler with Signature Verification
import crypto from 'crypto';
import { Request, Response } from 'express';
import type { WebhookPayload, WebhookEvent } from '../types/customerio-webhooks';
export class CustomerIOWebhookHandler {
private signingSecret: string;
constructor(signingSecret: string) {
this.signingSecret = signingSecret;
}
verifySignature(payload: string, signature: string): boolean {
const expectedSignature = crypto
.createHmac('sha256', this.signingSecret)
.update(payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
}
async handleRequest(req: , : ): <> {
signature = req.[] ;
payload = .(req.);
(!signature || !.(payload, signature)) {
res.().({ : });
;
}
: = req.;
{
.(webhookPayload.);
res.().({ : webhookPayload.. });
} (: ) {
.(, error);
res.().({ : error. });
}
}
(: []): <> {
( event events) {
.(event);
}
}
(: ): <> {
.(, event.);
(event.) {
:
.(event);
;
:
.(event);
;
:
.(event);
;
:
.(event);
;
:
.(event);
;
:
.(event);
;
:
.();
}
}
(: ): <> {
.();
}
(: ): <> {
.();
}
(: ): <> {
.();
}
(: ): <> {
.();
}
(: ): <> {
.();
}
(: ): <> {
.();
}
}
Step 3: Express Router Setup
import { Router } from 'express';
import { CustomerIOWebhookHandler } from '../lib/webhook-handler';
const router = Router();
const webhookHandler = new CustomerIOWebhookHandler(
process.env.CUSTOMERIO_WEBHOOK_SECRET!
);
router.use('/customerio', express.raw({ type: 'application/json' }));
router.post('/customerio', async (req, res) => {
req.body = JSON.parse(req.body.toString());
await webhookHandler.handleRequest(req, res);
});
export default router;
Step 4: Event Queue for Reliability
import { Queue, Worker } from 'bullmq';
import Redis from 'ioredis';
import type { WebhookEvent } from '../types/customerio-webhooks';
const connection = new Redis(process.env.REDIS_URL!);
const webhookQueue = new Queue('customerio-webhooks', { connection });
export async function queueWebhookEvent(event: WebhookEvent): Promise<void> {
await webhookQueue.add(event.metric, event, {
removeOnComplete: 1000,
removeOnFail: 5000,
attempts: 3,
backoff: {
type: 'exponential',
delay: 1000
}
});
}
const worker = new (
,
(job) => {
: = job.;
.(, event.);
(event.) {
:
(event);
;
:
(event);
;
}
},
{ connection }
);
worker.(, {
.();
});
worker.(, {
.(, err);
});
Step 5: Reporting API Integration
import { APIClient, RegionUS } from '@customerio/track';
const apiClient = new APIClient(process.env.CUSTOMERIO_APP_API_KEY!, {
region: RegionUS
});
export async function getDeliveryMetrics(
period: 'day' | 'week' | 'month' = 'day'
): Promise<DeliveryMetrics> {
const response = await fetch(
`https://api.customer.io/v1/metrics/email/${period}`,
{
headers: {
'Authorization': `Bearer ${process.env.CUSTOMERIO_APP_API_KEY}`
}
}
);
return response.json();
}
export async function getCampaignMetrics(campaignId: number): Promise<CampaignMetrics> {
const response = await (
,
{
: {
:
}
}
);
response.();
}
Step 6: Data Warehouse Streaming
import { BigQuery } from '@google-cloud/bigquery';
import type { WebhookEvent } from '../types/customerio-webhooks';
const bigquery = new BigQuery();
const dataset = bigquery.dataset('customerio_events');
const table = dataset.table('delivery_events');
export async function streamToBigQuery(events: WebhookEvent[]): Promise<void> {
const rows = events.map(event => ({
event_id: event.event_id,
event_type: event.metric,
customer_id: event.data.customer_id,
email_address: event.data.email_address,
campaign_id: event.data.campaign_id,
delivery_id: event.data.delivery_id,
timestamp: new Date(event.timestamp * ).(),
: ().()
}));
table.(rows);
}
Output
- Webhook event type definitions
- Signature verification handler
- Express router setup
- Event queue for reliability
- Reporting API integration
- Data warehouse streaming
Error Handling
| Issue | Solution |
|---|
| Invalid signature | Verify webhook secret matches |
| Duplicate events | Use event_id for deduplication |
| Queue overflow | Increase worker concurrency |
Resources
Next Steps
After webhook setup, proceed to customerio-performance-tuning for optimization.