Skip to main content

Webhook Event Reference

12 Events

Complete reference for all webhook event types including payload schemas, TypeScript interfaces, and example payloads for each event.

Event Wildcards

You can subscribe to all events in a category using wildcards like user.* or payment.*. Use * to subscribe to all events.

Common Properties

Every webhook event includes these common properties regardless of the event type.

Base Event Structure
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
  }
}
PropertyTypeDescription
idrequiredstringUnique identifier for idempotency. Format: evt_xxxxxxxxxxxx
typerequiredstringEvent type in category.action format
timestamprequiredstringISO 8601 timestamp (e.g., 2024-01-15T10:30:00.000Z)
versionrequiredstringAPI version for backward compatibility (e.g., 2024-01-01)
app_idrequiredstringYour application identifier
environmentrequiredstringEnvironment where the event originated
datarequiredobjectEvent-specific payload (varies by event type)
metadatarequiredobjectDelivery 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

user.created

Triggered when a new user account is created through any method (signup, OAuth, or admin creation).

types.ts
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.

types.ts
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.

types.ts
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

User data in webhook payloads is retained for 30 days after deletion for audit purposes. After this period, payloads are permanently redacted.

Session Events

session.created

Triggered when a user successfully authenticates and a new session is created.

types.ts
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.

types.ts
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.

types.ts
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.

types.ts
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.

types.ts
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.

types.ts
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.

types.ts
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

Failed payments are automatically retried up to 4 times over 14 days. Use the 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.

types.ts
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.

types.ts
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.

webhook-handler.ts
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 })
}