Webhook Payload Format
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
- What fields should every webhook envelope include?
- How do you version webhook payloads?
- What should be included in the
data.objectfield? - How do you handle backward-compatible changes?
- 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
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