posthog-webhooks-events
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChinesePostHog Webhooks & Events
PostHog Webhooks与事件处理
Overview
概述
Securely handle PostHog webhooks with signature validation and replay protection.
通过签名验证和重放攻击防护,安全处理PostHog webhook。
Prerequisites
前置条件
- PostHog webhook secret configured
- HTTPS endpoint accessible from internet
- Understanding of cryptographic signatures
- Redis or database for idempotency (optional)
- 已配置PostHog webhook密钥
- 可从互联网访问的HTTPS端点
- 了解加密签名相关知识
- Redis或数据库(用于幂等性处理,可选)
Webhook Endpoint Setup
Webhook端点设置
Express.js
Express.js
typescript
import express from 'express';
import crypto from 'crypto';
const app = express();
// IMPORTANT: Raw body needed for signature verification
app.post('/webhooks/posthog',
express.raw({ type: 'application/json' }),
async (req, res) => {
const signature = req.headers['x-posthog-signature'] as string;
const timestamp = req.headers['x-posthog-timestamp'] as string;
if (!verifyPostHogSignature(req.body, signature, timestamp)) {
return res.status(401).json({ error: 'Invalid signature' });
}
const event = JSON.parse(req.body.toString());
await handlePostHogEvent(event);
res.status(200).json({ received: true });
}
);typescript
import express from 'express';
import crypto from 'crypto';
const app = express();
// IMPORTANT: Raw body needed for signature verification
app.post('/webhooks/posthog',
express.raw({ type: 'application/json' }),
async (req, res) => {
const signature = req.headers['x-posthog-signature'] as string;
const timestamp = req.headers['x-posthog-timestamp'] as string;
if (!verifyPostHogSignature(req.body, signature, timestamp)) {
return res.status(401).json({ error: 'Invalid signature' });
}
const event = JSON.parse(req.body.toString());
await handlePostHogEvent(event);
res.status(200).json({ received: true });
}
);Signature Verification
签名验证
typescript
function verifyPostHogSignature(
payload: Buffer,
signature: string,
timestamp: string
): boolean {
const secret = process.env.POSTHOG_WEBHOOK_SECRET!;
// Reject old timestamps (replay attack protection)
const timestampAge = Date.now() - parseInt(timestamp) * 1000;
if (timestampAge > 300000) { // 5 minutes
console.error('Webhook timestamp too old');
return false;
}
// Compute expected signature
const signedPayload = `${timestamp}.${payload.toString()}`;
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(signedPayload)
.digest('hex');
// Timing-safe comparison
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
}typescript
function verifyPostHogSignature(
payload: Buffer,
signature: string,
timestamp: string
): boolean {
const secret = process.env.POSTHOG_WEBHOOK_SECRET!;
// Reject old timestamps (replay attack protection)
const timestampAge = Date.now() - parseInt(timestamp) * 1000;
if (timestampAge > 300000) { // 5 minutes
console.error('Webhook timestamp too old');
return false;
}
// Compute expected signature
const signedPayload = `${timestamp}.${payload.toString()}`;
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(signedPayload)
.digest('hex');
// Timing-safe comparison
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
}Event Handler Pattern
事件处理模式
typescript
type PostHogEventType = 'resource.created' | 'resource.updated' | 'resource.deleted';
interface PostHogEvent {
id: string;
type: PostHogEventType;
data: Record<string, any>;
created: string;
}
const eventHandlers: Record<PostHogEventType, (data: any) => Promise<void>> = {
'resource.created': async (data) => { /* handle */ },
'resource.updated': async (data) => { /* handle */ },
'resource.deleted': async (data) => { /* handle */ }
};
async function handlePostHogEvent(event: PostHogEvent): Promise<void> {
const handler = eventHandlers[event.type];
if (!handler) {
console.log(`Unhandled event type: ${event.type}`);
return;
}
try {
await handler(event.data);
console.log(`Processed ${event.type}: ${event.id}`);
} catch (error) {
console.error(`Failed to process ${event.type}: ${event.id}`, error);
throw error; // Rethrow to trigger retry
}
}typescript
type PostHogEventType = 'resource.created' | 'resource.updated' | 'resource.deleted';
interface PostHogEvent {
id: string;
type: PostHogEventType;
data: Record<string, any>;
created: string;
}
const eventHandlers: Record<PostHogEventType, (data: any) => Promise<void>> = {
'resource.created': async (data) => { /* handle */ },
'resource.updated': async (data) => { /* handle */ },
'resource.deleted': async (data) => { /* handle */ }
};
async function handlePostHogEvent(event: PostHogEvent): Promise<void> {
const handler = eventHandlers[event.type];
if (!handler) {
console.log(`Unhandled event type: ${event.type}`);
return;
}
try {
await handler(event.data);
console.log(`Processed ${event.type}: ${event.id}`);
} catch (error) {
console.error(`Failed to process ${event.type}: ${event.id}`, error);
throw error; // Rethrow to trigger retry
}
}Idempotency Handling
幂等性处理
typescript
import { Redis } from 'ioredis';
const redis = new Redis(process.env.REDIS_URL);
async function isEventProcessed(eventId: string): Promise<boolean> {
const key = `posthog:event:${eventId}`;
const exists = await redis.exists(key);
return exists === 1;
}
async function markEventProcessed(eventId: string): Promise<void> {
const key = `posthog:event:${eventId}`;
await redis.set(key, '1', 'EX', 86400 * 7); // 7 days TTL
}typescript
import { Redis } from 'ioredis';
const redis = new Redis(process.env.REDIS_URL);
async function isEventProcessed(eventId: string): Promise<boolean> {
const key = `posthog:event:${eventId}`;
const exists = await redis.exists(key);
return exists === 1;
}
async function markEventProcessed(eventId: string): Promise<void> {
const key = `posthog:event:${eventId}`;
await redis.set(key, '1', 'EX', 86400 * 7); // 7 days TTL
}Webhook Testing
Webhook测试
bash
undefinedbash
undefinedUse PostHog CLI to send test events
Use PostHog CLI to send test events
posthog webhooks trigger resource.created --url http://localhost:3000/webhooks/posthog
posthog webhooks trigger resource.created --url http://localhost:3000/webhooks/posthog
Or use webhook.site for debugging
Or use webhook.site for debugging
curl -X POST https://webhook.site/your-uuid
-H "Content-Type: application/json"
-d '{"type": "resource.created", "data": {}}'
-H "Content-Type: application/json"
-d '{"type": "resource.created", "data": {}}'
undefinedcurl -X POST https://webhook.site/your-uuid
-H "Content-Type: application/json"
-d '{"type": "resource.created", "data": {}}'
-H "Content-Type: application/json"
-d '{"type": "resource.created", "data": {}}'
undefinedInstructions
操作步骤
Step 1: Register Webhook Endpoint
步骤1:注册Webhook端点
Configure your webhook URL in the PostHog dashboard.
在PostHog仪表盘中配置你的webhook URL。
Step 2: Implement Signature Verification
步骤2:实现签名验证
Use the signature verification code to validate incoming webhooks.
使用签名验证代码来验证传入的webhook。
Step 3: Handle Events
步骤3:处理事件
Implement handlers for each event type your application needs.
为应用所需的每种事件类型实现处理逻辑。
Step 4: Add Idempotency
步骤4:添加幂等性处理
Prevent duplicate processing with event ID tracking.
通过事件ID跟踪来防止重复处理。
Output
输出结果
- Secure webhook endpoint
- Signature validation enabled
- Event handlers implemented
- Replay attack protection active
- 安全的webhook端点
- 已启用签名验证
- 已实现事件处理逻辑
- 已激活重放攻击防护
Error Handling
错误处理
| Issue | Cause | Solution |
|---|---|---|
| Invalid signature | Wrong secret | Verify webhook secret |
| Timestamp rejected | Clock drift | Check server time sync |
| Duplicate events | Missing idempotency | Implement event ID tracking |
| Handler timeout | Slow processing | Use async queue |
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 无效签名 | 密钥错误 | 验证webhook密钥 |
| 时间戳被拒绝 | 时钟偏移 | 检查服务器时间同步 |
| 重复事件 | 缺少幂等性处理 | 实现事件ID跟踪 |
| 处理程序超时 | 处理速度过慢 | 使用异步队列 |
Examples
示例
Testing Webhooks Locally
本地测试Webhook
bash
undefinedbash
undefinedUse ngrok to expose local server
Use ngrok to expose local server
ngrok http 3000
ngrok http 3000
Send test webhook
Send test webhook
curl -X POST https://your-ngrok-url/webhooks/posthog
-H "Content-Type: application/json"
-d '{"type": "test", "data": {}}'
-H "Content-Type: application/json"
-d '{"type": "test", "data": {}}'
undefinedcurl -X POST https://your-ngrok-url/webhooks/posthog
-H "Content-Type: application/json"
-d '{"type": "test", "data": {}}'
-H "Content-Type: application/json"
-d '{"type": "test", "data": {}}'
undefinedResources
参考资源
Next Steps
下一步
For performance optimization, see .
posthog-performance-tuning如需性能优化,请查看。
posthog-performance-tuning