How to set up Zendesk messaging message delivered webhooks

Stevia Putri
Written by

Stevia Putri

Reviewed by

Stanley Nicholas

Last edited February 20, 2026

Expert Verified

Banner image for How to set up Zendesk messaging message delivered webhooks

Webhooks turn your Zendesk into a real-time notification engine. Instead of polling for updates, webhooks push data to your systems the moment something happens. For messaging specifically, delivery webhooks let you track whether messages actually reach your customers. Not just handed off to WhatsApp or Twilio. Actually delivered.

This matters because "sent" and "delivered" are two different things. A message might leave Zendesk, fail at the carrier level, and you'd never know without delivery tracking. Or it might sit undelivered because a customer's phone is offline. Delivery webhooks give you visibility into the full message lifecycle.

In this guide, you'll learn how to set up Zendesk message delivery webhooks from scratch. We'll cover the three delivery event types, walk through configuration step by step, and provide working code you can adapt for your own implementation.

What you'll need

Before diving in, make sure you have:

  • A Zendesk account with admin access (webhook configuration requires admin permissions)
  • An HTTPS endpoint to receive webhook payloads (a development server works fine for testing)
  • Basic familiarity with JSON and HTTP concepts
  • Optionally: a tool like Postman or curl for testing

If you're building this for production, you'll also want to think through authentication, error handling, and idempotency. We'll touch on all of those.

Understanding Zendesk message delivery events

Zendesk's messaging platform sends three types of delivery events. Each tells you something different about where your message stands.

The three delivery event types

Event TypeWhen It FiresWhat It Tells You
conversation:message:delivery:channelMessage handed to channel APIThe channel (WhatsApp, Twilio, etc.) accepted the message
conversation:message:delivery:userMessage reached user's deviceThe customer actually received the message
conversation:message:delivery:failureDelivery failedSomething went wrong; error details included

Here's how the flow works. When you send a message, it first hits the channel's API. If that API accepts the message, you get a delivery:channel event. For some channels (like WhatsApp or SMS via Twilio), you might later get a delivery:user event confirming the message reached the device. If anything fails along the way, you get delivery:failure with specific error information.

The isFinalEvent flag tells you whether more events are coming. When isFinalEvent: true, this is the last webhook you'll receive for that message. When false, the channel supports user-level delivery confirmation, so expect a follow-up.

Channel support varies

Not all channels can confirm delivery to the user. Some only confirm they received the message from Zendesk. Here's the breakdown:

Channels with full delivery confirmation (channel + user):

  • WhatsApp
  • Twilio SMS
  • MessageBird
  • iOS SDK, Android SDK, Unity SDK, Web Widget

Channels with channel delivery only:

  • Facebook Messenger
  • Instagram
  • LINE
  • Telegram
  • Viber
  • WeChat
  • X (Twitter)
  • Apple Messages for Business

For channels without user confirmation, the delivery:channel event will have isFinalEvent: true. That's your signal that no further updates are coming.

Why this matters for your implementation

If you're building analytics on top of these webhooks, you'll want to account for this variation. A "delivered" metric means different things depending on the channel. For WhatsApp, it's device-level confirmation. For Facebook Messenger, it just means Facebook's API accepted the message.

Message delivery flow from API handoff to customer's device
Message delivery flow from API handoff to customer's device

Step 1: Create a webhook in Zendesk Admin Center

Let's walk through creating your first delivery webhook.

First, navigate to Admin Center > Apps and integrations > Webhooks. If you don't see the Webhooks option, check that your role has the proper permissions. Learn more in the Zendesk webhooks documentation.

Click Create webhook. You'll see a form with several fields:

Name: Give it something descriptive like "Message Delivery Tracker" or "Production Delivery Events."

Endpoint URL: This is where Zendesk will POST the webhook payloads. For development, you can use a tool like ngrok to expose a local server. For production, this should be an HTTPS URL on your infrastructure.

HTTP method: Select POST. Delivery events always use POST.

Request format: Select JSON.

Don't save yet. We'll configure event subscriptions and authentication in the next steps.

Step 2: Subscribe to delivery events

In the webhook creation form, find the Event subscriptions section. This is where you specify which events should trigger your webhook.

For message delivery tracking, subscribe to these three events:

  1. conversation:message:delivery:channel
  2. conversation:message:delivery:user
  3. conversation:message:delivery:failure

You can subscribe to additional events if needed (like conversation:message for all messages), but for delivery tracking specifically, these three are what you want.

Event filtering options:

If your integration is part of the switchboard, you can control whether you receive events for conversations you're not actively managing. The deliverStandbyEvents setting filters this. Most integrations set this to false to only receive events for active conversations.

Save the webhook. Zendesk will assign it a unique ID (starting with 01). Copy this ID. You'll need it for testing and for connecting to triggers if you go that route.

Step 3: Set up webhook authentication

Webhooks without authentication are a security risk. Anyone who discovers your endpoint URL could send fake events. Zendesk supports several authentication methods.

Available authentication options

MethodBest ForHow It Works
API keySimple integrationsHeader-based key verification
Basic authLegacy system compatibilityUsername/password in Authorization header
Bearer tokenModern API integrationsOAuth or JWT token verification

For most implementations, API key authentication strikes the right balance between security and simplicity.

Configuring in Zendesk

In the webhook form, expand the Authentication section. Select your preferred method and enter the credentials:

  • For API key: specify the header name (e.g., X-API-Key) and the key value
  • For Bearer token: enter the token Zendesk should include
  • For Basic auth: provide username and password

Verifying webhook signatures

Zendesk can sign webhooks using HMAC-SHA256. This lets you verify that payloads actually came from Zendesk, not an attacker. See the Zendesk webhook signing documentation for detailed implementation guidance.

To enable this, you'll need to:

  1. Generate a signing secret (Zendesk provides this when you enable signing)
  2. Verify the X-Zendesk-Webhook-Signature header in your endpoint
  3. Compute the HMAC-SHA256 of the payload using your secret
  4. Compare it to the signature header

Here's a simple Node.js example:

const crypto = require('crypto');

function verifyWebhookSignature(payload, signature, secret) {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(payload, 'utf8')
    .digest('base64');
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );
}

Step 4: Build your webhook endpoint

Now for the fun part: building the endpoint that receives these webhooks.

Basic requirements

Your endpoint must:

  • Use HTTPS (Zendesk rejects HTTP URLs)
  • Respond within 10 seconds
  • Return a 2xx status code for successful processing
  • Handle duplicate events gracefully (idempotency)

Sample webhook receiver (Node.js/Express)

Here's a minimal but production-ready endpoint:

const express = require('express');
const app = express();

app.use(express.json());

app.post('/webhooks/zendesk-delivery', (req, res) => {
  // Acknowledge receipt immediately
  res.status(200).send('OK');

  // Process events asynchronously
  const events = req.body.events || [];

  for (const event of events) {
    handleDeliveryEvent(event);
  }
});

function handleDeliveryEvent(event) {
  const { type, payload } = event;

  switch (type) {
    case 'conversation:message:delivery:channel':
      console.log(`Message ${payload.message.id} delivered to ${payload.destination.type}`);
      break;

    case 'conversation:message:delivery:user':
      console.log(`Message ${payload.message.id} reached user`);
      break;

    case 'conversation:message:delivery:failure':
      console.error(`Message ${payload.message.id} failed:`, payload.error);
      break;
  }
}

app.listen(3000, () => {
  console.log('Webhook endpoint listening on port 3000');
});

Key things this example does:

  1. Responds immediately (200 OK) before processing
  2. Processes events asynchronously
  3. Handles each event type separately
  4. Extracts relevant IDs for logging/debugging

Handling retries and idempotency

Zendesk retries failed webhooks (4xx/5xx responses, timeouts) up to 3-5 times depending on the error. This means your endpoint might receive the same event multiple times.

To handle this, implement idempotency:

const processedEvents = new Set(); // Use Redis in production

function handleDeliveryEvent(event) {
  if (processedEvents.has(event.id)) {
    console.log(`Skipping duplicate event: ${event.id}`);
    return;
  }

  // Process the event...

  processedEvents.add(event.id);
}

In production, use Redis or a database to track processed event IDs with TTL (time to live) so the deduplication window matches Zendesk's retry behavior.

Step 5: Understanding the delivery webhook payload

Let's break down what actually arrives in these webhooks.

Common payload structure

All delivery events share this wrapper structure:

{
  "app": {
    "id": "5fb29b20fee5422428712475"
  },
  "webhook": {
    "id": "5ff5e98b0d0c6d8925594923",
    "version": "v2"
  },
  "events": [
    {
      "id": "5ff7595eafcaab0a685ff889",
      "createdAt": "2021-01-07T18:56:30.666Z",
      "type": "conversation:message:delivery:channel",
      "payload": {
        "conversation": { "id": "2d4fd3d00715d1e64611e248" },
        "user": { "id": "71268330a47f5c4b541fce46" },
        "destination": {
          "type": "whatsapp",
          "integrationId": "5ff75853b1c3000a6ad4f7f5"
        },
        "message": { "id": "5ff7595eb1c3000a6ad4f7fb" },
        "isFinalEvent": false,
        "externalMessages": [
          { "id": "wamid.HBgNNTUxQTk4MDUz5Tg4MRU..." }
        ]
      }
    }
  ]
}

Key fields explained

FieldWhy It Matters
events[].idUnique event ID. Use for deduplication.
events[].payload.conversation.idLinks the event to a specific conversation.
events[].payload.message.idThe specific message this event refers to.
events[].payload.destination.typeWhich channel (whatsapp, twilio, etc.).
events[].payload.isFinalEventWhether more events are coming for this message.
events[].payload.externalMessagesChannel's native message IDs for debugging.

Failure event specifics

When delivery fails, the payload includes an error object:

{
  "error": {
    "code": "bad_request",
    "message": "Message failed to send because more than 24 hours have passed since the customer last replied.",
    "underlyingError": {
      "errors": [{
        "code": 131047,
        "title": "Re-engagement message",
        "message": "Re-engagement message"
      }]
    }
  }
}

The underlyingError field contains the raw error from the channel's API. This is invaluable for debugging. WhatsApp's error codes, for example, tell you exactly why a message was rejected.

Step 6: Test your webhook implementation

Before going live, you need to verify everything works.

Using Zendesk's webhook tester

In the webhook configuration page, click Test webhook. Zendesk will send a test payload to your endpoint and show you the response. This is useful for verifying connectivity and authentication.

However, the test payload is generic. It won't be a real delivery event. For realistic testing, you need to trigger actual message flows.

Testing with real messages

  1. Send a test message through Zendesk messaging
  2. Check your endpoint logs for incoming webhooks
  3. Verify the payload structure matches expectations
  4. Confirm your event handling logic processes each type correctly

For channels that support user delivery (like WhatsApp), you can verify the two-event flow. The first webhook should have isFinalEvent: false. The second (user delivery) should have isFinalEvent: true.

Checking webhook activity logs

Zendesk keeps webhook logs for 7 days. Navigate to Admin Center > Apps and integrations > Webhooks, select your webhook, and view the Activity tab. This shows recent deliveries, response codes, and any errors.

If you're not seeing expected webhooks, check:

  • Is the webhook active (not paused)?
  • Are the event subscriptions correct?
  • Is your endpoint responding with 2xx status codes?

Troubleshooting common issues

Even with careful setup, things go wrong. Here are the most common issues and how to fix them.

Webhook not firing

If you're not receiving webhooks when messages are sent:

  • Verify the webhook is active (not paused or in draft state)
  • Check that the correct event types are subscribed
  • For trigger-connected webhooks, verify the trigger conditions are being met
  • Check the webhook activity logs for failure patterns

Authentication errors (401/403)

If Zendesk reports authentication failures:

  • Verify your endpoint is checking the right header
  • Confirm the API key/token hasn't expired
  • Check for case sensitivity in header names
  • If using signature verification, ensure you're computing the HMAC correctly

Timeouts and circuit breaker

Zendesk has a 10-second timeout. If your endpoint takes longer:

  • Respond immediately (200 OK) and process asynchronously
  • Use a message queue (SQS, RabbitMQ, etc.) for heavy processing
  • Monitor processing times and optimize slow operations

The circuit breaker trips at 70% error rate or 1,000+ errors in 5 minutes. If this happens, Zendesk pauses your webhook. You'll need to fix the underlying issue and reactivate the webhook manually.

Missing delivery events

If you're seeing some events but not others:

  • Check the channel support matrix. Not all channels send user delivery confirmations.
  • For WhatsApp specifically, user delivery events may be delayed or suppressed if the message is received outside a certain time window.
  • Verify isFinalEvent behavior. If it's true at the channel level, no user delivery event will follow.

Retry logic and circuit breaker thresholds for webhook resilience
Retry logic and circuit breaker thresholds for webhook resilience

Best practices for production

Once your webhook is working, consider these production recommendations.

Error handling and monitoring

  • Log all webhook receipts with event IDs for debugging
  • Set up alerts for elevated error rates
  • Monitor endpoint response times
  • Track delivery rates by channel to spot issues early

Security

  • Always use HTTPS in production
  • Implement webhook signature verification
  • Rotate API keys periodically
  • Don't log sensitive payload data

Data retention

Delivery events contain message metadata. Consider:

  • How long to retain event logs
  • Whether to store full payloads or just key fields
  • Compliance requirements (GDPR, etc.) for message data

When to poll vs. use webhooks

Webhooks are ideal for real-time updates. But if you need historical data or missed events, you might need to supplement with polling. The List Messages API can fill gaps.

Streamline messaging workflows with eesel AI

Building custom webhook endpoints takes engineering time. You need to handle authentication, retries, idempotency, and error cases. Then you need to build the actual business logic on top of those events.

We built eesel AI to handle this complexity for you. Instead of managing webhooks yourself, you get turnkey messaging automation with delivery tracking built in. Our AI teammate integrates with Zendesk (and Freshdesk, Gorgias, and others) to handle conversations end-to-end.

If you're looking for a faster path to AI-powered messaging, check out our complete guide to Zendesk messaging webhooks. And if you want to compare options, see our breakdown of the best AI chatbots for Zendesk.

Frequently Asked Questions

Only if you've subscribed to delivery events and the message is sent through a supported channel. Channel delivery events fire for all messages. User delivery events only fire for channels that support device-level confirmation.
It depends on your use case. For debugging, 7-30 days is usually sufficient. For analytics or compliance, you might need longer retention. Zendesk's own webhook logs are kept for 7 days, so if you need longer history, you must store events yourself.
Yes. One webhook can receive delivery events from all channels. The destination.type field in the payload tells you which channel each event came from.
Trigger webhooks fire based on ticket conditions you define. Event webhooks (like delivery events) fire for every occurrence of the subscribed event type. For delivery tracking, you want event webhooks because you need to capture every delivery status change, not just specific ticket conditions.
User delivery events can arrive hours after the message was sent (if a phone was offline, for example). Build your system to handle out-of-order events. Use message IDs to correlate events with the original send operation, and don't assume events arrive in chronological order.
Webhooks are available on Zendesk Suite plans and Support plans with the Webhooks add-on. Messaging itself requires a Zendesk Suite subscription. Check your plan details or contact Zendesk support to confirm webhook access.

Share this post

Stevia undefined

Article by

Stevia Putri

Stevia Putri is a marketing generalist at eesel AI, where she helps turn powerful AI tools into stories that resonate. She’s driven by curiosity, clarity, and the human side of technology.