Webhook Payload Reference

When an email arrives, Conduit parses the message. It then delivers the message to your target URL as an HTTP POST, with a JSON body and a set of request headers.


Request headers

Every delivery carries these HTTP request headers:

Header Description
Content-Type application/json
X-Conduit-Signature The HMAC-SHA256 signature of the raw request body (see Verifying the signature)
X-Conduit-Transaction-Id A unique ID for the received email and its delivery attempt, for example txn_01KXWZKRDC0F812ABQRGQQJKXH. Record it on your side to match your logs against the Conduit logs

Every delivery request also carries the custom_headers from the webhook.


JSON body

The request body is a JSON object with these fields:

Field Type Always present Description
message_id string Yes The SMTP Message-ID. Conduit generates a value when the sender sends none
from string Yes The SMTP envelope sender address (MAIL FROM)
to array of strings Yes The SMTP envelope recipient addresses (RCPT TO)
subject string No The Subject header. Absent when the message has none
date string No The Date header, converted to RFC 3339 UTC. Absent when the message has none, or when Conduit cannot parse it
text string No The plain-text body. Absent when the message has no plain-text part
html string No The HTML body. Absent when the message has no HTML part
headers object No The non-standard headers, as { "Header-Name": ["value", ...] }. Absent when the message has none

The headers object holds only the headers that are not already top-level fields. Conduit always excludes these headers from headers: Message-Id, From, To, Subject, Date, Content-Type, Mime-Version.

Example payload

{
  "message_id": "<abc123@mail.example.com>",
  "from": "alerts@monitoring.example.com",
  "to": ["webhook-01HX...@incoming.conduit.email"],
  "subject": "Disk usage above 90% on web-01",
  "date": "2024-01-15T10:00:00Z",
  "text": "Disk usage on web-01 is at 91%. Please investigate.",
  "html": "<p>Disk usage on web-01 is at <strong>91%</strong>. Please investigate.</p>",
  "headers": {
    "X-Priority": ["1"],
    "X-Mailer": ["AlertManager 2.4"]
  }
}

Verifying the signature

Every delivery carries an X-Conduit-Signature header:

X-Conduit-Signature: sha256=<hex-encoded-digest>

The signature is an HMAC-SHA256 of the raw request body bytes, keyed with the webhook secret. Verify it before you process the payload:

import hmac
import hashlib

def verify_signature(body: bytes, secret: str, header: str) -> bool:
    expected = "sha256=" + hmac.new(
        secret.encode(), body, hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, header)

Reject a request when the signature does not match. Conduit computes the signature over the exact bytes that it sends, so read the raw body before you parse the JSON.


Payload templates

By default, Conduit sends the full JSON structure above. For a different shape, set a payload template on the webhook. A common reason is a POST directly to a Slack incoming webhook.

In the web UI

Open the Edit form of the webhook (/app/webhooks/{id}/edit) and enter the template in the Payload template field.

Using the API

Set the payload_template field when you create or update a webhook.

A payload template is a Go text/template string. Conduit renders it against the parsed email. The rendered output replaces the whole default JSON body, so it must match what your target URL expects.

Template variables

Variable Type Description
{{.MessageID}} string The SMTP Message-ID
{{.From}} string The envelope sender address
{{.To}} []string The envelope recipient addresses
{{.Subject}} string The subject line (an empty string when absent)
{{.Date}} string The date in RFC 3339 UTC (an empty string when absent)
{{.Text}} string The plain-text body (an empty string when absent)
{{.HTML}} string The HTML body (an empty string when absent)
{{.Headers}} map[string][]string The non-standard headers

Example: Slack incoming webhook

{"text": "*{{.Subject}}*\nFrom: {{.From}}\n\n{{.Text}}"}

Example: read one non-standard header

{"priority": "{{index (index .Headers "X-Priority") 0}}", "subject": "{{.Subject}}"}

Conduit validates a template when you create or update the webhook. It rejects an invalid template with a 422 response and the error code payload_template_invalid.


Custom headers

The custom_headers field on a webhook adds static name and value headers to every delivery request. Use it to send an API key or a shared secret to your endpoint, next to the Conduit signature:

{
  "custom_headers": {
    "Authorization": "Bearer my-api-key",
    "X-My-App-Source": "conduit"
  }
}

A header name must use valid HTTP token characters (RFC 7230). The reserved header X-Conduit-Signature cannot be overridden, and Conduit rejects it with the error code custom_headers_invalid. Conduit always sets X-Conduit-Transaction-Id after it applies the custom headers, so that header cannot be overridden either.