> ## Documentation Index
> Fetch the complete documentation index at: https://docs.zeplo.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhooks

> Receive real-time event notifications about messages, conversations, and contacts with verified payloads, automatic retries, and easy testing tools.

# Webhooks

Zeplo Webhooks deliver real-time event notifications to your server whenever something happens in your WhatsApp environment. Get instant updates when messages are received, delivered, or read; when conversations open or close; and when contacts are created or updated. Every payload is cryptographically signed so you can verify authenticity, and failed deliveries are retried automatically with exponential backoff.

> **Event Types**
>
> Subscribe to the exact events you care about — message lifecycle, conversation state changes, and contact updates.

> **Payload Verification**
>
> Every webhook payload includes a cryptographic signature. Verify it on your server to confirm the request originated from Zeplo.

> **Retry Logic**
>
> Failed deliveries are retried up to 5 times with exponential backoff over 24 hours. Inspect delivery logs to diagnose persistent failures.

> **Webhook Testing**
>
> Use the built-in test sender in the Zeplo Dashboard to send sample payloads to your endpoint during development.

## Basic Usage

**JavaScript/TypeScript**

```typescript theme={null}
import { Zeplo } from "@zeplo/sdk";

const zeplo = new Zeplo({ apiKey: process.env.ZEPLO_API_KEY });

// Create a webhook endpoint
const webhook = await zeplo.webhooks.create({
  url: "https://api.example.com/webhooks/zeplo",
  events: [
    "message.received",
    "message.delivered",
    "message.read",
    "conversation.opened",
    "conversation.closed",
    "contact.created",
  ],
  secret: "whsec_your_signing_secret",
  active: true,
});

// List webhooks
const { webhooks } = await zeplo.webhooks.list();

// Verify a webhook payload (Express example)
import crypto from "crypto";

function verifyWebhook(payload: string, signature: string, secret: string): boolean {
  const expected = crypto
    .createHmac("sha256", secret)
    .update(payload)
    .digest("hex");
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );
}

// Delete a webhook
await zeplo.webhooks.delete(webhook.id);

console.log(`Webhook registered: ${webhook.url}`);
```

**Python**

```python theme={null}
import os
import hmac
import hashlib
from zeplo import Zeplo

zeplo = Zeplo(api_key=os.environ["ZEPLO_API_KEY"])

# Create a webhook endpoint
webhook = zeplo.webhooks.create(
    url="https://api.example.com/webhooks/zeplo",
    events=[
        "message.received",
        "message.delivered",
        "message.read",
        "conversation.opened",
        "conversation.closed",
        "contact.created",
    ],
    secret="whsec_your_signing_secret",
    active=True,
)

# List webhooks
result = zeplo.webhooks.list()
webhooks = result.webhooks

# Verify a webhook payload (Flask example)
def verify_webhook(payload: bytes, signature: str, secret: str) -> bool:
    expected = hmac.new(
        secret.encode(), payload, hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(signature, expected)

# Delete a webhook
zeplo.webhooks.delete(webhook.id)

print(f"Webhook registered: {webhook.url}")
```

> [!NOTE]
>
> The webhook `secret` is used to generate HMAC-SHA256 signatures. Store it securely and never expose it in client-side code. Rotate the secret periodically via the Dashboard or API.

## Event Types

| Event | Description | Key Payload Fields |
| - | - | - |
| `message.received` | A contact sent a message to your number | `messageId`, `from`, `text`, `timestamp`, `type` |
| `message.delivered` | A sent message was delivered to the contact's device | `messageId`, `to`, `timestamp` |
| `message.read` | A delivered message was read by the contact | `messageId`, `to`, `timestamp` |
| `conversation.opened` | A new conversation was started or reopened | `conversationId`, `contactId`, `channel` |
| `conversation.closed` | A conversation was closed (manually or by timeout) | `conversationId`, `contactId`, `reason` |
| `contact.created` | A new contact was added to your account | `contactId`, `phone`, `name`, `source` |

## Best Practices

> All webhook endpoints **must use HTTPS** with a valid SSL certificate. Zeplo will not deliver events to HTTP URLs or endpoints with self-signed certificates. Use a reverse proxy like Nginx or a tunneling tool like ngrok during local development.

> Always verify the `X-Zeplo-Signature` header before processing a webhook payload. This confirms the request came from Zeplo and not a third party. Use constant-time comparison (`crypto.timingSafeEqual` or `hmac.compare_digest`) to prevent timing attacks.

> Design your webhook handler to be **idempotent**. Zeplo may deliver the same event more than once during retries. Use the `eventId` field in the payload header to deduplicate events before processing.

> Your endpoint must respond with a **200-level status code within 10 seconds**. If your processing takes longer, acknowledge immediately and handle the work asynchronously (e.g., enqueue to a job queue). Slow responses trigger retries and can eventually disable the webhook.

> **[API Reference: List Webhooks](https://docs.zeplo.ai/docs/api-reference/webhooks/list-webhooks.md)**
>
> Explore the full request schema, event subscription options, and response format for the webhooks endpoint.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.