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.