The Event-Driven APIs & Webhooks pattern is used when servers need to notify consumers of changes asynchronously, rather than requiring clients to poll for updates. This pattern exposes:

  • A subscription endpoint (e.g., POST /webhook-subscriptions)
  • A callback delivery contract (e.g., POSTs sent to consumer endpoints)
  • Optional retry, signing, and idempotency behavior

Common examples:

  • Order status changed
  • Payment succeeded/failed
  • Inventory updated
  • User profile updated
  • Long-running job completed

This pattern complements — rather than replaces — synchronous APIs.

11.1. Overview

Event-driven APIs allow publishers (your system) to send notifications or full payloads to subscribers (clients) whenever relevant changes occur.

Characteristics:

  • Push-based: server initiates the callback
  • Asynchronous delivery
  • Optional event payload describing what changed
  • Retry strategy for delivery failures
  • Signature verification / HMAC for security
  • Often used as part of an integration platform or webhook framework

This pattern significantly reduces polling load and improves responsiveness.

11.2. When to Use This Pattern

Use Webhooks / Event-Driven APIs when:

  • Clients need to know about mutations as soon as they happen.
  • Polling would be expensive, inefficient, or slow.
  • Third-party systems need to react to changes (e.g., CRM, ERP, payment systems).
  • You want to support loose coupling between systems.
  • Human-facing products need real-time updates without using streaming (e.g., mobile apps receiving notifications via your backend).

Examples:

  • POST /webhook-subscriptions
  • Outbound events such as:

    • order.shipped
    • payment.failed
    • customer.updated

11.3. When NOT to Use This Pattern

Avoid webhooks when:

  • The client cannot expose a public HTTP endpoint.
  • Delivery needs strict ordering guarantees (use message queues instead).
  • Consumer systems cannot handle duplicate deliveries.
  • You need high-frequency updates (streaming APIs may be a better fit).
  • Security requirements disallow sending data to third-party URLs.

Also avoid using webhooks:

  • As a replacement for synchronous APIs
  • For data that requires strong consistency (webhooks are eventually consistent)

11.4. What the Pattern Looks Like

A canonical event-driven workflow:

  1. Client subscribes to events:
POST /webhook-subscriptions
{
  "eventType": "order.shipped",
  "callbackUrl": "https://client.example.com/webhooks/order-shipped"
}
  1. Server validates and stores the subscription.

  2. When an event occurs, server delivers it:

POST https://client.example.com/webhooks/order-shipped
{
  "eventId": "evt_567",
  "eventType": "order.shipped",
  "occurredAt": "2024-05-04T12:00:00Z",
  "data": {
    "orderId": "or_12345",
    "trackingNumber": "1Z999"
  }
}
  1. Client responds with:
200 OK

If delivery fails, server retries according to its retry strategy.


11.5. Anti-Patterns to Avoid

No verification/signature on webhook calls → Anyone could impersonate your service.

No idempotency → If retries happen, the consumer may duplicate work.

Assuming guaranteed delivery → Webhooks are best-effort; always include retry + dead-letter strategies.

Sending overly large event payloads → Webhooks are best for lightweight messages; large payloads should be fetched via follow-up API calls.

Failing to store subscription metadata → E.g., invalidating credentials or callback URLs without lifecycle events.

11.6. OpenAPI Examples

This includes:

  • POST /webhook-subscriptions
  • GET /webhook-subscriptions/{subscriptionId}
  • Webhook event schema (as delivered to client)
openapi: 3.0.3
info:
  title: Webhooks API - Event-Driven APIs & Webhooks Pattern Example
  version: 1.0.0
servers:
  - url: https://api.example.com

paths:
  /webhook-subscriptions:
    post:
      summary: Create a webhook subscription
      tags: [Webhooks]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateWebhookSubscriptionRequest'
            examples:
              default:
                value:
                  eventType: "order.shipped"
                  callbackUrl: "https://client.example.com/webhooks/order-shipped"
                  secret: "whsec_abc123"
      responses:
        '201':
          description: Webhook subscription created
          headers:
            Location:
              schema:
                type: string
                example: https://api.example.com/webhook-subscriptions/sub_12345
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WebhookSubscription'
              examples:
                default:
                  value:
                    subscriptionId: "sub_12345"
                    eventType: "order.shipped"
                    callbackUrl: "https://client.example.com/webhooks/order-shipped"
                    secret: "whsec_abc123"

  /webhook-subscriptions/{subscriptionId}:
    get:
      summary: Get webhook subscription details
      tags: [Webhooks]
      parameters:
        - in: path
          name: subscriptionId
          schema:
            type: string
            example: sub_12345
          required: true
      responses:
        '200':
          description: Subscription details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WebhookSubscription'
        '404':
          description: Not Found

components:
  schemas:
    CreateWebhookSubscriptionRequest:
      type: object
      properties:
        eventType:
          type: string
          example: "order.shipped"
        callbackUrl:
          type: string
          example: "https://client.example.com/webhooks/order-shipped"
        secret:
          type: string
          example: "whsec_abc123"
      required:
        - eventType
        - callbackUrl

    WebhookSubscription:
      type: object
      properties:
        subscriptionId:
          type: string
          example: "sub_12345"
        eventType:
          type: string
          example: "order.shipped"
        callbackUrl:
          type: string
          example: "https://client.example.com/webhooks/order-shipped"
        secret:
          type: string
          example: "whsec_abc123"
      required:
        - subscriptionId
        - eventType
        - callbackUrl

    WebhookEvent:
      type: object
      description: Payload that your API delivers to a subscriber's webhook endpoint.
      properties:
        eventId:
          type: string
          example: "evt_567"
        eventType:
          type: string
          example: "order.shipped"
        occurredAt:
          type: string
          format: date-time
          example: "2024-05-04T12:00:00Z"
        data:
          type: object
          example:
            orderId: "or_12345"
            trackingNumber: "1Z999"
      required:
        - eventId
        - eventType
        - occurredAt
        - data

11.7. Visualizing the Event-Driven & Webhooks Pattern (Mermaid)

sequenceDiagram
    autonumber
    participant C as Client System
    participant API as Webhooks API
    participant EVT as Event Producer (Orders)
    participant WH as Client Webhook Endpoint

    Note over C,API: Client subscribes to events
    C->>API: POST /webhook-subscriptions<br/>{ eventType, callbackUrl, secret }
    API-->>C: 201 Created<br/>{ subscriptionId }

    Note over EVT,API: An event occurs in domain system
    EVT->>API: publish(event: order.shipped)

    Note over API,WH: API delivers webhook event
    API->>WH: POST callbackUrl<br/>{ eventId, eventType, data }
    WH-->>API: 200 OK

    alt Delivery fails
        API->>WH: Retry with exponential backoff
    end