Skip to content

Webhook Payload Format

DodaTech 4 min read

title: "Webhook Payload Format" description: "Learn how to design webhook payloads including event envelopes, data structures, versioning, and best practices for schema design." weight: 14 date: 2026-06-28 lastmod: 2026-06-28 tags: ["apis", "webhooks"]


Webhook payloads carry the event data from provider to consumer. A well-designed payload format ensures clarity, evolvability, and ease of integration for consumers.

## What You'll Learn

- Event envelope structure
- Payload schema design
- Versioning strategies
- Pagination for large payloads
- Minimizing payload size

## Why It Matters

Consumers depend on your payload format. Breaking changes cause integration failures. A well-designed payload format ensures long-term compatibility and developer satisfaction.

## Real-World Use

Stripe's webhook payload format includes an envelope with ID, type, creation time, and nested data object. This structure has remained stable for years while the API evolved, demonstrating the value of a well-designed envelope.

## Flow Chart

```mermaid
flowchart LR
    A[Webhook Payload] --> B[Envelope]
    A --> C[Data Object]
    B --> D[id]
    B --> E[type]
    B --> F[created_at]
    B --> G[api_version]
    C --> H[Event-specific fields]
    C --> I[Related resources]

Code Examples

Example 1: Standard Webhook Envelope

// Standard webhook payload envelope
const webhookPayload = {
  // Envelope fields
  id: 'evt_3N9X8Y2Z7Q1A5B6C',
  type: 'payment.intent.succeeded',
  api_version: '2023-10-01',
  created_at: '2026-06-28T12:00:00Z',
  
  // Event data
  data: {
    object: {
      id: 'pi_3N9X8Y2Z7Q1A5B6C',
      amount: 2999,
      currency: 'usd',
      status: 'succeeded',
      customer: 'cus_123456',
      payment_method: 'pm_789012',
    },
    previous_attributes: null, // For update events
  },
  
  // Account/application context
  account: 'acct_123456',
  application: null,
  
  // Request information
  request: {
    id: 'req_ABC123',
    idempotency_key: 'key_xyz789',
  },
};

Expected output: A complete webhook payload with envelope fields, event data, context, and request information.

Example 2: Event Type Specific Payloads

// Different payloads per event type
const webhookSchemas = {
  'user.created': {
    type: 'user.created',
    data: {
      object: {
        id: 'user_123',
        email: 'user@example.com',
        name: 'Alice Smith',
        created_at: '2026-06-28T12:00:00Z',
      },
    },
  },

  'order.updated': {
    type: 'order.updated',
    data: {
      object: {
        id: 'order_456',
        status: 'shipped',
        total: 5999,
        items: [
          { product_id: 'prod_1', quantity: 2, price: 1999 },
          { product_id: 'prod_2', quantity: 1, price: 3999 },
        ],
        shipping_address: {
          line1: '123 Main St',
          city: 'San Francisco',
          state: 'CA',
          zip: '94105',
        },
      },
      previous_attributes: {
        status: 'processing',
      },
    },
  },

  'subscription.deleted': {
    type: 'subscription.deleted',
    data: {
      object: {
        id: 'sub_789',
        customer: 'cus_123456',
        canceled_at: '2026-06-28T12:00:00Z',
        reason: 'payment_failed',
      },
    },
  },
};

Expected output: Event-specific payloads with appropriate fields for each event type, following a consistent envelope structure.

Example 3: Versioned Payload Schema

// Version 1 payload
const v1Payload = {
  id: 'evt_001',
  type: 'invoice.paid',
  api_version: '2023-01-01',
  data: {
    object: {
      id: 'in_001',
      amount_due: 5000,
      amount_paid: 5000,
      status: 'paid',
    },
  },
};

// Version 2 payload (backward compatible)
const v2Payload = {
  id: 'evt_001',
  type: 'invoice.paid',
  api_version: '2024-06-01',
  data: {
    object: {
      id: 'in_001',
      amount_due: 5000,
      amount_paid: 5000,
      amount_remaining: 0, // Added in v2
      status: 'paid',
      paid_at: '2026-06-28T12:00:00Z', // Added in v2
      payment_methods: [ // Added in v2
        { type: 'card', last4: '4242' },
      ],
    },
  },
};

// Version 3 payload (breaking change - new object structure)
const v3Payload = {
  id: 'evt_001',
  type: 'invoice.paid',
  api_version: '2025-01-01',
  data: {
    object: {
      id: 'in_001',
      total: { amount: 5000, currency: 'usd' }, // Changed from flat amount
      paid: { amount: 5000, at: '2026-06-28T12:00:00Z' },
      status: 'paid',
    },
  },
};

Expected output: Multiple API versions with backward-compatible additions in v2 and a clearly marked breaking change in v3.

Common Mistakes

Mistake Explanation
Inconsistent envelope structure All events should use the same envelope fields (id, type, created_at) for predictable parsing
Making breaking changes without versioning Always version your webhook API and maintain backward compatibility
Including too much data Large payloads increase delivery time and storage costs; include only essential data
Using opaque IDs without context Include a resource type prefix (e.g., cus_, pi_, evt_) to help consumers identify object types
Not including event ID for deduplication Every webhook must have a unique ID for consumers to detect duplicates
Changing field types between versions Never change a field's type; add new fields instead of modifying existing ones

Practice Questions

  1. What fields should every webhook envelope include?
  2. How do you version webhook payloads?
  3. What should be included in the data.object field?
  4. How do you handle backward-compatible changes?
  5. What is the purpose of previous_attributes?

Challenge

Design a versioned webhook payload schema for a project management API. Support events for task created, updated, deleted, and commented on. Create v1 and v2 payloads where v2 adds subtask support without breaking v1 consumers.

FAQ

Should webhook payloads include related resources?

Include related resource IDs and, optionally, expanded objects for commonly accessed relations. Avoid including entire related resource graphs.

How large should webhook payloads be?

Keep payloads under 1MB, ideally under 100KB. For larger data, include a reference and let consumers fetch the full resource via API.

Should I use snake_case or camelCase?

Both are common. Choose one convention and apply it consistently. JSON Schema can enforce case conventions.

How do I handle null fields?

Include null fields explicitly rather than omitting them. This makes the schema predictable and easier to parse.

Should webhook payloads include timestamps?

Yes, include ISO 8601 UTC timestamps for both the event creation and the original resource timestamps.

What is the recommended date format?

Use ISO 8601 format (e.g., 2026-06-28T12:00:00Z) in UTC. Avoid timezone offsets or local times.

Mini Project

Design a webhook payload schema system for an e-commerce platform. Create payload specifications for 10 event types across orders, products, customers, and inventory. Include versioning, envelope structure, and schema documentation with examples.

What's Next

Learn about webhook signature verification

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro