Webhooks Reference

Relay uses webhooks for two purposes:

  1. Incoming Webhooks: Receive events from third-party services (email providers, messaging platforms, payment processors)
  2. Outgoing Webhooks: Send events from Relay to your external systems

This document covers both incoming webhook endpoints and outgoing webhook configuration.


Incoming Webhooks Overview

Relay provides REST webhook endpoints to receive events from integrated services. These endpoints handle signature verification, event parsing, and ticket/conversation creation.

ServiceEndpointMethod
Email (Mailgun/SendGrid/Postmark)/api/webhooks/inbound-emailPOST
Twilio SMS/api/webhooks/smsPOST
WhatsApp Business/api/webhooks/whatsappGET, POST
Facebook Messenger/api/webhooks/messengerGET, POST
Dialpad/api/webhooks/dialpadPOST
Shopify/api/webhooks/shopifyPOST
Shopify GDPR/api/webhooks/shopify/gdprPOST
Stripe/api/webhooks/stripePOST
Stripe Connect/api/webhooks/stripe-connectPOST
Slack/api/webhooks/slackPOST
Resend/api/webhooks/resendPOST
Resend (legacy alias)/api/webhooks/emailPOST
Amazon Seller Central (SNS)/api/amazon/webhookPOST

Email Webhooks

Endpoint

POST /api/webhooks/inbound-email

Supported Providers

  • Mailgun - Inbound email routing
  • SendGrid - Inbound Parse webhook
  • Postmark - Inbound webhook
  • Cloudmailin - Email to HTTP

Mailgun Configuration

  1. Go to Mailgun Dashboard → Receiving → Create Route
  2. Set the route expression to match your support email domain
  3. Set the action to forward to your webhook URL:
    https://your-domain.com/api/webhooks/inbound-email
    

Signature Verification:

Mailgun includes signature data in the form payload:

  • timestamp - Unix timestamp
  • token - Random string
  • signature - HMAC SHA256 of timestamp + token
# Verification formula
signature = HMAC-SHA256(api_key, timestamp + token)

Payload Format (multipart/form-data):

FieldDescription
fromSender email address
senderEnvelope sender
recipientRecipient email
subjectEmail subject
body-plainPlain text body
body-htmlHTML body
stripped-textBody without quoted text
stripped-htmlHTML without quoted text
message-headersJSON array of headers
In-Reply-ToReply threading header
ReferencesMessage reference chain
attachment-countNumber of attachments
attachment-{n}File attachments

SendGrid Configuration

  1. Go to SendGrid → Settings → Inbound Parse
  2. Add your domain and set the destination URL:
    https://your-domain.com/api/webhooks/inbound-email
    

Signature Verification:

SendGrid uses two headers:

  • x-twilio-email-event-webhook-signature - ECDSA signature
  • x-twilio-email-event-webhook-timestamp - Unix timestamp

Payload Format (multipart/form-data):

FieldDescription
fromSender email
toRecipient email
subjectEmail subject
textPlain text body
htmlHTML body
headersFull email headers
envelopeJSON with sender/recipients
attachmentsNumber of attachments
attachment{n}File attachments

Postmark Configuration

  1. Go to Postmark → Servers → Inbound
  2. Set the webhook URL:
    https://your-domain.com/api/webhooks/inbound-email
    

Signature Verification:

Include your webhook token in the URL or x-postmark-token header.

Payload Format (application/json):

{
  "From": "sender@example.com",
  "FromName": "John Doe",
  "To": "support@company.com",
  "Subject": "Help needed",
  "TextBody": "Plain text content",
  "HtmlBody": "<p>HTML content</p>",
  "ReplyTo": "sender@example.com",
  "MessageID": "unique-id",
  "Headers": [
    {"Name": "In-Reply-To", "Value": "<message-id>"}
  ],
  "Attachments": [
    {
      "Name": "file.pdf",
      "ContentType": "application/pdf",
      "Content": "base64-encoded-content"
    }
  ]
}

Email Threading

Relay automatically threads emails to existing tickets using:

  1. In-Reply-To header - Direct reply reference
  2. References header - Full message chain
  3. Subject pattern - [Ticket #123] in subject line
  4. Message-ID tracking - Stored for each conversation

Email Classification

Incoming emails are classified as:

TypeDescription
inboundNew customer message
bounceDelivery failure notification
outbound_copyBCC copy of sent email
auto_replyAutomatic reply (vacation, etc.)
dialpad_voicemailVoicemail notification from Dialpad

Auto-replies and bounces are handled specially to avoid creating unnecessary tickets.


SMS Webhooks (Twilio)

Endpoint

POST /api/webhooks/sms

Configuration

  1. Go to Twilio Console → Phone Numbers → Manage → Active Numbers
  2. Select your number and set the webhook URL for incoming messages:
    https://your-domain.com/api/webhooks/sms
    
  3. Set the HTTP method to POST

Payload Format (application/x-www-form-urlencoded):

FieldDescription
FromSender phone number (E.164 format)
ToYour Twilio number
BodyMessage content
MessageSidUnique message identifier
NumMediaNumber of media attachments
MediaUrl0, MediaUrl1, ...URLs to media files
MediaContentType0, ...MIME types of media

Response Format:

The endpoint returns TwiML XML. For auto-replies:

<?xml version="1.0" encoding="UTF-8"?>
<Response>
  <Message>Thank you for your message. We'll respond shortly.</Message>
</Response>

For no response:

<?xml version="1.0" encoding="UTF-8"?>
<Response></Response>

SMS Threading

SMS conversations are threaded by phone number. All messages from the same number are grouped into a single ticket thread.


WhatsApp Webhooks

Endpoint

  • GET /api/webhooks/whatsapp - Webhook verification
  • POST /api/webhooks/whatsapp - Receive messages

Configuration

  1. Go to Meta Developer Portal → Your App → WhatsApp → Configuration
  2. Set the webhook URL:
    https://your-domain.com/api/webhooks/whatsapp
    
  3. Set a verify token (store this in your integration settings)
  4. Subscribe to: messages

Webhook Verification

Meta verifies your webhook with a GET request:

Query Parameters:

ParameterDescription
hub.modeAlways subscribe
hub.verify_tokenYour configured token
hub.challengeChallenge string to echo back

Response: Return the hub.challenge value with status 200.

Message Webhook

Headers:

HeaderDescription
x-hub-signature-256HMAC SHA256 signature

Signature Verification:

signature = HMAC-SHA256(app_secret, raw_body)
# Compare with x-hub-signature-256 header (format: sha256=...)

Payload Format:

{
  "object": "whatsapp_business_account",
  "entry": [
    {
      "id": "WHATSAPP_BUSINESS_ACCOUNT_ID",
      "changes": [
        {
          "value": {
            "messaging_product": "whatsapp",
            "metadata": {
              "display_phone_number": "15551234567",
              "phone_number_id": "PHONE_NUMBER_ID"
            },
            "contacts": [
              {
                "profile": {"name": "Customer Name"},
                "wa_id": "15559876543"
              }
            ],
            "messages": [
              {
                "from": "15559876543",
                "id": "wamid.xxx",
                "timestamp": "1234567890",
                "type": "text",
                "text": {"body": "Hello, I need help"}
              }
            ]
          },
          "field": "messages"
        }
      ]
    }
  ]
}

Message Types

TypeDescription
textPlain text message
imageImage with optional caption
documentFile attachment
audioVoice message
videoVideo message
locationLocation share
contactsContact card
interactiveButton/list response

Facebook Messenger Webhooks

Endpoint

  • GET /api/webhooks/messenger - Webhook verification
  • POST /api/webhooks/messenger - Receive messages

Configuration

  1. Go to Meta Developer Portal → Your App → Messenger → Settings
  2. Set the webhook URL:
    https://your-domain.com/api/webhooks/messenger
    
  3. Set a verify token
  4. Subscribe to: messages, messaging_postbacks

Webhook Verification

Same as WhatsApp - return hub.challenge when hub.verify_token matches.

Message Webhook

Headers:

HeaderDescription
x-hub-signature-256HMAC SHA256 signature

Payload Format:

{
  "object": "page",
  "entry": [
    {
      "id": "PAGE_ID",
      "time": 1234567890,
      "messaging": [
        {
          "sender": {"id": "PSID"},
          "recipient": {"id": "PAGE_ID"},
          "timestamp": 1234567890,
          "message": {
            "mid": "MESSAGE_ID",
            "text": "Hello, I need help",
            "attachments": [
              {
                "type": "image",
                "payload": {"url": "https://..."}
              }
            ]
          }
        }
      ]
    }
  ]
}

Event Types

EventDescription
messageUser sent a message
postbackUser clicked a button (e.g., Get Started)
readUser read a message
deliveryMessage was delivered

Dialpad Webhooks

Endpoint

POST /api/webhooks/dialpad

Configuration

  1. In Dialpad Admin → Company Settings → Integrations
  2. Or via Dialpad API, register webhook URL:
    https://your-domain.com/api/webhooks/dialpad
    

Signature Verification

Header: x-dialpad-signature

signature = HMAC-SHA256(webhook_secret, raw_body)

Call Events

Payload Format:

{
  "call_id": "unique-call-id",
  "state": "connected",
  "direction": "inbound",
  "target": {
    "phone_number": "+15551234567",
    "name": "Support Line"
  },
  "contact": {
    "phone_number": "+15559876543",
    "name": "Customer Name"
  },
  "started_at": 1234567890,
  "answered_at": 1234567891,
  "duration": 120
}

Call States

StateDescription
callingOutbound call initiated
ringingCall ringing
connectedCall answered
holdCall on hold
hangupCall ended
missedCall not answered
voicemailWent to voicemail
voicemail_uploadedVoicemail recording ready
transcriptionTranscription available
recordingCall recording ready

SMS Events

{
  "event_type": "sms",
  "direction": "inbound",
  "from_number": "+15559876543",
  "to_number": "+15551234567",
  "text": "Message content",
  "timestamp": 1234567890
}

Voicemail Processing

When a voicemail is received:

  1. voicemail state event fires
  2. voicemail_uploaded fires when audio is ready
  3. transcription fires when text transcription is ready
  4. Relay creates a ticket with the voicemail audio and transcription

Shopify Webhooks

Endpoint

POST /api/webhooks/shopify

Configuration

Webhooks are automatically registered when you connect your Shopify store. Relay subscribes to:

  • orders/create
  • orders/updated
  • customers/create
  • customers/update

Signature Verification

Header: x-shopify-hmac-sha256

signature = Base64(HMAC-SHA256(webhook_secret, raw_body))

Payload Format

Shopify sends standard webhook payloads. See Shopify Webhook Documentation.


Shopify GDPR Webhooks

Endpoint

POST /api/webhooks/shopify/gdpr

Topics

Shopify requires the following GDPR topics:

  • customers/data_request
  • customers/redact
  • shop/redact

Signature Verification

Header: x-shopify-hmac-sha256

Uses the Shopify API secret to validate HMAC signatures.


Amazon SNS Webhooks

Endpoint

POST /api/amazon/webhook

Purpose

Receives Amazon Seller Central notifications via SNS and processes order and return events.

Notes

  • Supports SNS subscription confirmation
  • Verifies SNS signing certificate host before processing

Stripe Webhooks

Endpoint

POST /api/webhooks/stripe

Configuration

  1. Go to Stripe Dashboard → Developers → Webhooks
  2. Add endpoint:
    https://your-domain.com/api/webhooks/stripe
    
  3. Select events to subscribe to

Signature Verification

Header: stripe-signature

Uses Stripe's official signature verification. See Stripe Webhook Signatures.

Subscribed Events

  • checkout.session.completed
  • customer.subscription.created
  • customer.subscription.updated
  • customer.subscription.deleted
  • invoice.payment_succeeded
  • invoice.payment_failed

Outgoing Webhooks

Configure outgoing webhooks to receive events from Relay in your external systems.

Configuration

Procedure: webhooks.create

{
  url: string;            // Your webhook endpoint URL
  events: string[];       // Events to subscribe to
  secret?: string;        // Optional signing secret
  enabled?: boolean;      // default: true
}

Available Events

EventDescription
ticket.createdNew ticket created
ticket.updatedTicket fields changed
ticket.status_changedStatus changed
ticket.assignedTicket assigned
ticket.priority_changedPriority changed
conversation.createdNew message/reply added
customer.createdNew customer created
customer.updatedCustomer updated

Payload Format

All outgoing webhooks use a consistent format:

{
  "event": "ticket.created",
  "timestamp": "2024-01-15T10:30:00.000Z",
  "data": {
    "ticketId": "uuid",
    "ticketNumber": 1234,
    "subject": "Need help with...",
    "status": "open",
    "priority": "normal",
    "customerId": "uuid",
    "customerEmail": "customer@example.com"
  }
}

Signature Verification

If you provide a secret when creating the webhook, Relay signs payloads:

Header: x-relay-signature

signature = HMAC-SHA256(secret, raw_body)

Retry Policy

Failed webhook deliveries are retried:

  • Retry 1: After 1 minute
  • Retry 2: After 5 minutes
  • Retry 3: After 30 minutes
  • Retry 4: After 2 hours
  • Retry 5: After 24 hours

After 5 failed attempts, the webhook is marked as failed and disabled.

Managing Webhooks

List Webhooks:

curl "https://your-domain.com/api/trpc/webhooks.list" \
  -H "Cookie: your-session-cookie"

Create Webhook:

curl -X POST "https://your-domain.com/api/trpc/webhooks.create" \
  -H "Content-Type: application/json" \
  -H "Cookie: your-session-cookie" \
  -d '{
    "json": {
      "url": "https://your-server.com/webhook",
      "events": ["ticket.created", "ticket.status_changed"],
      "secret": "your-signing-secret"
    }
  }'

Update Webhook:

curl -X POST "https://your-domain.com/api/trpc/webhooks.update" \
  -H "Content-Type: application/json" \
  -H "Cookie: your-session-cookie" \
  -d '{
    "json": {
      "id": "webhook-uuid",
      "events": ["ticket.created", "conversation.created"],
      "enabled": true
    }
  }'

Delete Webhook:

curl -X POST "https://your-domain.com/api/trpc/webhooks.delete" \
  -H "Content-Type: application/json" \
  -H "Cookie: your-session-cookie" \
  -d '{"json":{"id":"webhook-uuid"}}'

Test Webhook:

curl -X POST "https://your-domain.com/api/trpc/webhooks.test" \
  -H "Content-Type: application/json" \
  -H "Cookie: your-session-cookie" \
  -d '{"json":{"id":"webhook-uuid"}}'

Best Practices

For Incoming Webhooks

  1. Always verify signatures - Never process unverified webhooks
  2. Return 200 quickly - Process events asynchronously to avoid timeouts
  3. Handle duplicates - Webhooks may be retried; use idempotency keys
  4. Log webhook payloads - Helps debug integration issues

For Outgoing Webhooks

  1. Use HTTPS endpoints - Webhook data may contain sensitive information
  2. Verify signatures - Validate the x-relay-signature header
  3. Handle retries - Your endpoint should be idempotent
  4. Respond quickly - Return 2xx within 30 seconds

Troubleshooting

Webhook not receiving events

  1. Check that the webhook URL is publicly accessible
  2. Verify the webhook is enabled in your integration settings
  3. Check your server logs for incoming requests
  4. Ensure your firewall allows requests from the service's IP ranges

Signature verification failing

  1. Ensure you're using the raw request body (not parsed JSON)
  2. Check that your secret matches the configured value
  3. Verify the signature algorithm matches (SHA256 vs SHA1)

Missing events

  1. Check that you've subscribed to the correct event types
  2. Verify the integration is properly connected
  3. Check the webhook delivery logs in Relay dashboard