Event Wildcards
user.* or payment.*. Use * to subscribe to all events.Common Properties
Every webhook event includes these common properties regardless of the event type.
interface WebhookEvent<T = unknown> {
// Unique identifier for this event
id: string
// Event type (e.g., 'user.created')
type: string
// ISO 8601 timestamp when the event occurred
timestamp: string
// API version for the payload format
version: string
// Your application ID
app_id: string
// Environment (production, staging, development)
environment: 'production' | 'staging' | 'development'
// Event-specific data payload
data: T
// Delivery metadata
metadata: {
webhook_id: string
attempt: number
max_attempts: number
}
}| Property | Type | Description |
|---|---|---|
idrequired | string | Unique identifier for idempotency. Format: evt_xxxxxxxxxxxx |
typerequired | string | Event type in category.action format |
timestamprequired | string | ISO 8601 timestamp (e.g., 2024-01-15T10:30:00.000Z) |
versionrequired | string | API version for backward compatibility (e.g., 2024-01-01) |
app_idrequired | string | Your application identifier |
environmentrequired | string | Environment where the event originated |
datarequired | object | Event-specific payload (varies by event type) |
metadatarequired | object | Delivery metadata including webhook ID and attempt number |
Event Categories
Events are organized into categories based on the resource they relate to. Click on any event to see its detailed payload schema.
User Events
Session Events
Subscription Events
subscription.createdFired when a new subscription is created
subscription.updatedFired when subscription details change
subscription.cancelledFired when a subscription is cancelled
Payment Events
User Events
user.created
Triggered when a new user account is created through any method (signup, OAuth, or admin creation).
interface UserCreatedEvent {
id: string
type: 'user.created'
timestamp: string
version: string
app_id: string
environment: 'production' | 'staging' | 'development'
data: {
id: string
email: string
name: string | null
avatar_url: string | null
email_verified: boolean
created_at: string
auth_provider: 'email' | 'google' | 'github' | 'apple'
metadata: Record<string, unknown>
}
metadata: {
webhook_id: string
attempt: number
max_attempts: number
}
}user.updated
Triggered when any user profile data is modified, including name, email, avatar, or custom metadata.
interface UserUpdatedEvent {
id: string
type: 'user.updated'
timestamp: string
version: string
app_id: string
environment: 'production' | 'staging' | 'development'
data: {
id: string
email: string
name: string | null
avatar_url: string | null
email_verified: boolean
updated_at: string
// Fields that changed in this update
changed_fields: string[]
// Previous values for changed fields
previous_values: Record<string, unknown>
}
metadata: {
webhook_id: string
attempt: number
max_attempts: number
}
}user.deleted
Triggered when a user account is permanently deleted. The payload contains a snapshot of the user data at deletion time.
interface UserDeletedEvent {
id: string
type: 'user.deleted'
timestamp: string
version: string
app_id: string
environment: 'production' | 'staging' | 'development'
data: {
id: string
email: string
name: string | null
deleted_at: string
deletion_reason: 'user_request' | 'admin_action' | 'policy_violation' | 'inactivity'
// Redacted for GDPR compliance after 30 days
data_retained_until: string
}
metadata: {
webhook_id: string
attempt: number
max_attempts: number
}
}Data Retention
Session Events
session.created
Triggered when a user successfully authenticates and a new session is created.
interface SessionCreatedEvent {
id: string
type: 'session.created'
timestamp: string
version: string
app_id: string
environment: 'production' | 'staging' | 'development'
data: {
session_id: string
user_id: string
created_at: string
expires_at: string
ip_address: string
user_agent: string
device: {
type: 'desktop' | 'mobile' | 'tablet' | 'unknown'
os: string
browser: string
}
location: {
country: string
region: string
city: string
} | null
auth_method: 'password' | 'oauth' | 'magic_link' | 'passkey' | '2fa'
}
metadata: {
webhook_id: string
attempt: number
max_attempts: number
}
}session.ended
Triggered when a session is terminated, either by user logout, expiration, or forced invalidation.
interface SessionEndedEvent {
id: string
type: 'session.ended'
timestamp: string
version: string
app_id: string
environment: 'production' | 'staging' | 'development'
data: {
session_id: string
user_id: string
created_at: string
ended_at: string
duration_seconds: number
end_reason: 'logout' | 'expired' | 'revoked' | 'password_change' | 'security'
}
metadata: {
webhook_id: string
attempt: number
max_attempts: number
}
}Subscription Events
subscription.created
Triggered when a new subscription is created, either through checkout or a free trial.
interface SubscriptionCreatedEvent {
id: string
type: 'subscription.created'
timestamp: string
version: string
app_id: string
environment: 'production' | 'staging' | 'development'
data: {
id: string
user_id: string
plan_id: string
plan_name: string
status: 'active' | 'trialing' | 'past_due'
current_period_start: string
current_period_end: string
trial_end: string | null
cancel_at_period_end: boolean
quantity: number
currency: string
unit_amount: number
interval: 'month' | 'year'
created_at: string
}
metadata: {
webhook_id: string
attempt: number
max_attempts: number
}
}subscription.updated
Triggered when subscription details change, such as plan upgrades, downgrades, or quantity changes.
interface SubscriptionUpdatedEvent {
id: string
type: 'subscription.updated'
timestamp: string
version: string
app_id: string
environment: 'production' | 'staging' | 'development'
data: {
id: string
user_id: string
plan_id: string
plan_name: string
status: 'active' | 'trialing' | 'past_due' | 'canceled' | 'unpaid'
current_period_start: string
current_period_end: string
cancel_at_period_end: boolean
quantity: number
currency: string
unit_amount: number
interval: 'month' | 'year'
updated_at: string
changed_fields: string[]
previous_values: Record<string, unknown>
}
metadata: {
webhook_id: string
attempt: number
max_attempts: number
}
}subscription.cancelled
Triggered when a subscription is cancelled. The subscription may remain active until the end of the billing period.
interface SubscriptionCancelledEvent {
id: string
type: 'subscription.cancelled'
timestamp: string
version: string
app_id: string
environment: 'production' | 'staging' | 'development'
data: {
id: string
user_id: string
plan_id: string
plan_name: string
status: 'canceled'
cancelled_at: string
cancel_at_period_end: boolean
current_period_end: string
cancellation_reason: string | null
cancellation_feedback: string | null
cancelled_by: 'user' | 'admin' | 'system'
}
metadata: {
webhook_id: string
attempt: number
max_attempts: number
}
}Payment Events
payment.succeeded
Triggered when a payment is successfully processed.
interface PaymentSucceededEvent {
id: string
type: 'payment.succeeded'
timestamp: string
version: string
app_id: string
environment: 'production' | 'staging' | 'development'
data: {
id: string
user_id: string
subscription_id: string | null
invoice_id: string | null
amount: number
currency: string
status: 'succeeded'
payment_method: {
type: 'card' | 'bank_transfer' | 'paypal'
last4: string | null
brand: string | null
}
receipt_url: string | null
created_at: string
}
metadata: {
webhook_id: string
attempt: number
max_attempts: number
}
}payment.failed
Triggered when a payment attempt fails. Includes error details for debugging.
interface PaymentFailedEvent {
id: string
type: 'payment.failed'
timestamp: string
version: string
app_id: string
environment: 'production' | 'staging' | 'development'
data: {
id: string
user_id: string
subscription_id: string | null
invoice_id: string | null
amount: number
currency: string
status: 'failed'
payment_method: {
type: 'card' | 'bank_transfer' | 'paypal'
last4: string | null
brand: string | null
}
error: {
code: string
message: string
decline_code: string | null
}
attempt_count: number
next_retry_at: string | null
created_at: string
}
metadata: {
webhook_id: string
attempt: number
max_attempts: number
}
}Payment Retry Schedule
next_retry_at field to know when the next attempt will occur.Referral Events
referral.completed
Triggered when a referred user completes signup using a referral link or code.
interface ReferralCompletedEvent {
id: string
type: 'referral.completed'
timestamp: string
version: string
app_id: string
environment: 'production' | 'staging' | 'development'
data: {
id: string
referrer_id: string
referred_id: string
referral_code: string
campaign_id: string | null
completed_at: string
}
metadata: {
webhook_id: string
attempt: number
max_attempts: number
}
}referral.rewarded
Triggered when referral rewards are distributed to either the referrer or the referred user.
interface ReferralRewardedEvent {
id: string
type: 'referral.rewarded'
timestamp: string
version: string
app_id: string
environment: 'production' | 'staging' | 'development'
data: {
id: string
referral_id: string
recipient_id: string
recipient_type: 'referrer' | 'referred'
reward_type: 'credit' | 'discount' | 'free_months' | 'custom'
reward_value: number
reward_currency: string | null
reward_description: string
rewarded_at: string
}
metadata: {
webhook_id: string
attempt: number
max_attempts: number
}
}Handling Events
Here is a complete example of how to handle multiple event types in your webhook endpoint.
import { verifyWebhook, type WebhookEvent } from '@sylphx/sdk'
type EventHandler<T> = (data: T) => Promise<void>
const handlers: Record<string, EventHandler<any>> = {
'user.created': async (data) => {
// Sync to CRM, send welcome email, etc.
await crm.createContact(data)
await email.sendWelcome(data.email, data.name)
},
'user.deleted': async (data) => {
// Clean up external systems
await crm.deleteContact(data.id)
await analytics.anonymize(data.id)
},
'subscription.created': async (data) => {
// Provision resources, update user tier
await provisioner.allocate(data.user_id, data.plan_id)
},
'payment.failed': async (data) => {
// Notify user, create support ticket
await email.sendPaymentFailed(data.user_id, data.error)
if (data.attempt_count >= 3) {
await support.createTicket({
user_id: data.user_id,
type: 'payment_issue',
priority: 'high',
})
}
},
'referral.rewarded': async (data) => {
// Notify recipient of reward
await email.sendRewardNotification(data.recipient_id, {
type: data.reward_type,
value: data.reward_value,
description: data.reward_description,
})
},
}
export async function POST(req: Request) {
const payload = await req.text()
const signature = req.headers.get('X-Webhook-Signature')!
let event: WebhookEvent
try {
event = verifyWebhook(payload, signature, process.env.WEBHOOK_SECRET!)
} catch {
return new Response('Invalid signature', { status: 401 })
}
// Check for duplicate delivery
const exists = await db.processedEvents.findUnique({
where: { eventId: event.id }
})
if (exists) {
return new Response('Already processed', { status: 200 })
}
// Get handler for event type
const handler = handlers[event.type]
if (handler) {
await handler(event.data)
} else {
console.log('Unhandled event type:', event.type)
}
// Mark as processed
await db.processedEvents.create({
data: { eventId: event.id, processedAt: new Date() }
})
return new Response('OK', { status: 200 })
}