clerk-webhooks
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseWebhooks
Webhooks
Prerequisite: Webhooks are asynchronous. Use for background tasks (sync, notifications), not synchronous flows.
前提条件:Webhooks 是异步的。适用于后台任务(同步、通知),而非同步流程。
Documentation Reference
文档参考
| Task | Link |
|---|---|
| Overview | https://clerk.com/docs/guides/development/webhooks/overview |
| Sync to database | https://clerk.com/docs/guides/development/webhooks/syncing |
| Debugging | https://clerk.com/docs/guides/development/webhooks/debugging |
| Event catalog | https://dashboard.clerk.com/~/webhooks (Event Catalog tab) |
Quick Start
快速开始
- Create endpoint at
app/api/webhooks/route.ts - Use from
verifyWebhook(req)@clerk/nextjs/webhooks - Dashboard → Webhooks → Add Endpoint
- Set in env
CLERK_WEBHOOK_SIGNING_SECRET - Make route public (not protected by middleware)
- 在 创建端点
app/api/webhooks/route.ts - 使用 中的
@clerk/nextjs/webhooksverifyWebhook(req) - 控制台 → Webhooks → 添加端点
- 在环境变量中设置
CLERK_WEBHOOK_SIGNING_SECRET - 将路由设置为公开(不被中间件保护)
Supported Events
支持的事件
User:
user.createduser.updateduser.deletedOrganization:
organization.createdorganization.updatedorganization.deletedOrganization Domain:
organizationDomain.createdorganizationDomain.updatedorganizationDomain.deletedOrganization Invitation:
organizationInvitation.createdorganizationInvitation.acceptedorganizationInvitation.revokedOrganization Membership:
organizationMembership.createdorganizationMembership.updatedorganizationMembership.deletedRoles:
role.createdrole.updatedrole.deletedPermissions:
permission.createdpermission.updatedpermission.deletedSession:
session.createdsession.updatedsession.endedsession.removedsession.revokedsession.pendingCommunication:
email.createdsms.createdInvitations:
invitation.createdinvitation.acceptedinvitation.revokedWaitlist:
waitlistEntry.createdwaitlistEntry.updatedFull catalog: Dashboard → Webhooks → Event Catalog
用户:
user.createduser.updateduser.deleted组织:
organization.createdorganization.updatedorganization.deleted组织域名:
organizationDomain.createdorganizationDomain.updatedorganizationDomain.deleted组织邀请:
organizationInvitation.createdorganizationInvitation.acceptedorganizationInvitation.revoked组织成员:
organizationMembership.createdorganizationMembership.updatedorganizationMembership.deleted角色:
role.createdrole.updatedrole.deleted权限:
permission.createdpermission.updatedpermission.deleted会话:
session.createdsession.updatedsession.endedsession.removedsession.revokedsession.pending通信:
email.createdsms.created邀请:
invitation.createdinvitation.acceptedinvitation.revoked等待列表:
waitlistEntry.createdwaitlistEntry.updated完整目录:控制台 → Webhooks → 事件目录
When to Sync
何时进行同步
Do sync when:
- Need other users' data (social features, profiles)
- Storing extra custom fields (birthday, country, bio)
- Building notifications or integrations
Don't sync when:
- Only need current user data (use session token)
- No custom fields (Clerk has everything)
- Need immediate access (webhooks are eventual consistency)
建议同步的场景:
- 需要其他用户的数据(社交功能、个人资料)
- 存储额外的自定义字段(生日、国家、个人简介)
- 构建通知或集成功能
不建议同步的场景:
- 仅需当前用户数据(使用会话令牌)
- 无自定义字段(Clerk 已包含所有所需数据)
- 需要即时访问数据(Webhooks 最终一致性)
Key Patterns
核心模式
Make Route Public
设置路由为公开
Webhooks come unsigned. Route must be public:
Ensure doesn't protect path.
clerkMiddleware()/api/webhooks(.*)Webhook 请求是未签名的,路由必须设置为公开:
确保 不会保护 路径。
clerkMiddleware()/api/webhooks(.*)Verify Webhook
验证 Webhook
Use correct import and single parameter:
typescript
import { verifyWebhook } from '@clerk/nextjs/webhooks'
const evt = await verifyWebhook(req) // Pass request directly使用正确的导入方式和单个参数:
typescript
import { verifyWebhook } from '@clerk/nextjs/webhooks'
const evt = await verifyWebhook(req) // Pass request directlyType-Safe Events
类型安全的事件
Narrow to specific event:
typescript
if (evt.type === 'user.created') {
// TypeScript knows evt.data structure
}缩小到特定事件类型:
typescript
if (evt.type === 'user.created') {
// TypeScript knows evt.data structure
}Handle All Three Events
处理全部三类事件
Don't only listen to . Also handle and .
user.createduser.updateduser.deleted不要只监听 ,同时也要处理 和 。
user.createduser.updateduser.deletedQueue Async Work
异步任务队列
Return 200 immediately, queue long operations:
typescript
await queue.enqueue('process-webhook', evt)
return new Response('Received', { status: 200 })立即返回200状态码,将长时间操作放入队列:
typescript
await queue.enqueue('process-webhook', evt)
return new Response('Received', { status: 200 })Webhook Reliability
Webhook 可靠性
Retries: Svix retries failed webhooks for up to 3 days. Return 2xx to succeed, 4xx/5xx to retry.
Replay: Failed webhooks can be replayed from Dashboard.
重试机制:Svix 会对失败的Webhook进行最多3天的重试。返回2xx状态码表示成功,4xx/5xx会触发重试。
重放功能:失败的Webhook可从控制台进行重放。
Common Pitfalls
常见问题
| Symptom | Cause | Fix |
|---|---|---|
| Verification fails | Wrong import or usage | Use |
| Route not found (404) | Wrong path | Use |
| Not authorized (401) | Route is protected | Make route public |
| No data in DB | Async job pending | Wait/check logs |
| Duplicate entries | Only handling | Also handle |
| Timeouts | Handler too slow | Queue async work |
| 症状 | 原因 | 解决方法 |
|---|---|---|
| 验证失败 | 导入方式或使用错误 | 使用 |
| 路由未找到(404) | 路径错误 | 使用 |
| 未授权(401) | 路由被保护 | 将路由设置为公开 |
| 数据库中无数据 | 异步任务待处理 | 等待或检查日志 |
| 重复条目 | 仅处理了 | 同时处理 |
| 超时 | 处理程序过慢 | 将异步工作放入队列 |
Testing & Deployment
测试与部署
Local: Use ngrok to tunnel to internet. Add ngrok URL to Dashboard endpoint.
localhost:3000Production: Update webhook endpoint URL to production domain. Copy signing secret to production env vars.
本地环境:使用ngrok将 隧道到公网。将ngrok URL添加到控制台的端点中。
localhost:3000生产环境:将Webhook端点URL更新为生产域名。将签名密钥复制到生产环境变量中。