T
transmit.

Webhooks & Events

Receive real-time HTTP notifications for email events. Configure endpoints, verify signatures, and track deliveries, opens, clicks, bounces, and complaints.

Receive real-time HTTP notifications when something happens to your emails. Deliveries, opens, clicks, bounces, and complaints are all sent to your endpoint as they occur.


Events

EventTrigger
email.sentEmail accepted for delivery
email.deliveredEmail delivered to recipient's mail server
email.openedRecipient opened the email
email.clickedRecipient clicked a link
email.bouncedDelivery failed (permanent or transient)
email.complainedRecipient marked as spam

Quick Start

  1. Go to Settings > Webhooks
  2. Click Add Endpoint
  3. Enter a name, your HTTPS URL, and select the events you want
  4. Click Create and copy your signing secret (shown only once)

Save your signing secret immediately. It cannot be retrieved later. If you lose it, you can rotate to a new one.


Payload Format

Every webhook sends a POST request with a JSON body:

{
  "event": "email.delivered",
  "timestamp": "2026-02-10T10:30:00.000Z",
  "data": {
    "messageId": "msg_1a2b3c4d5e6f",
    "recipient": "user@example.com",
    "campaignId": "cmp_xxxxx",
    "contactId": "cnt_xxxxx"
  }
}

The data object always includes messageId, recipient, campaignId, and contactId. Additional fields vary by event type.

Event-Specific Fields

email.delivered

FieldTypeDescription
processingTimeMsnumberTime from send to delivery (ms)
smtpResponsestringResponse from recipient's server

email.bounced

FieldTypeDescription
bounceTypestringpermanent or transient
bounceSubTypestringSub-category (e.g., General, NoEmail)
diagnosticCodestringSMTP diagnostic from receiving server

email.complained

FieldTypeDescription
feedbackTypestringComplaint feedback type (e.g., abuse)

email.opened

FieldTypeDescription
userAgentstringRecipient's email client
ipAddressstringRecipient's IP address

email.clicked

FieldTypeDescription
urlstringThe link that was clicked
userAgentstringRecipient's browser/client
ipAddressstringRecipient's IP address

email.sent includes only the base fields (no additional data).


Request Headers

Every webhook request includes these headers:

HeaderExampleDescription
Content-Typeapplication/jsonAlways JSON
X-Xmit-Eventemail.deliveredThe event type
X-Xmit-Timestamp2026-02-10T10:30:00.000ZWhen the event occurred
X-Xmit-Signaturesha256=a1b2c3...HMAC-SHA256 signature

Verifying Signatures

Every webhook is signed with your endpoint's secret using HMAC-SHA256. Always verify signatures to confirm the request came from Transmit.

The signature is computed over the raw JSON request body and sent in the X-Xmit-Signature header as sha256=<hex>.

Node.js

const crypto = require("crypto");

function verifyWebhook(rawBody, signatureHeader, secret) {
  const expected = crypto
    .createHmac("sha256", secret)
    .update(rawBody)
    .digest("hex");

  const received = signatureHeader.replace("sha256=", "");

  return crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(received)
  );
}

// Express example
app.post("/webhook", express.raw({ type: "application/json" }), (req, res) => {
  const signature = req.headers["x-xmit-signature"];
  const isValid = verifyWebhook(req.body, signature, process.env.WEBHOOK_SECRET);

  if (!isValid) {
    return res.status(401).send("Invalid signature");
  }

  const event = JSON.parse(req.body);
  console.log("Received:", event.event, event.data.messageId);
  res.sendStatus(200);
});

Python

import hmac
import hashlib

def verify_webhook(raw_body: bytes, signature_header: str, secret: str) -> bool:
    expected = hmac.new(
        secret.encode(),
        raw_body,
        hashlib.sha256
    ).hexdigest()

    received = signature_header.replace("sha256=", "")

    return hmac.compare_digest(expected, received)

Go

func verifyWebhook(body []byte, signatureHeader, secret string) bool {
    mac := hmac.New(sha256.New, []byte(secret))
    mac.Write(body)
    expected := hex.EncodeToString(mac.Sum(nil))

    received := strings.TrimPrefix(signatureHeader, "sha256=")

    return hmac.Equal([]byte(expected), []byte(received))
}

Always use constant-time comparison (timingSafeEqual, compare_digest, hmac.Equal) to prevent timing attacks.


Managing Endpoints

All webhook management is done from the dashboard at Settings > Webhooks.

Create an Endpoint

  1. Click Add Endpoint
  2. Give it a name (e.g., "Production")
  3. Enter your HTTPS URL
  4. Select the events you want to receive
  5. Click Create and copy the signing secret

Edit or Disable

Click on any endpoint to update its name, URL, or subscribed events. You can also toggle an endpoint on or off without deleting it. Re-enabling a disabled endpoint resets its failure count.

Rotate Secret

If your secret is compromised, click Rotate Secret on the endpoint. The old secret is invalidated immediately. Update your verification code with the new secret before processing further events.

Test Delivery

Click Send Test to deliver a sample email.delivered event to your endpoint. This lets you verify your server is receiving and processing webhooks correctly before going live.


Failure Handling

Transmit tracks consecutive delivery failures for each endpoint.

  • Timeout: Your endpoint must respond within 5 seconds
  • Success: Any 2xx status code. Resets the failure counter
  • Failure: Non-2xx response, timeout, or connection error. Increments the failure counter
  • Auto-disable: After 10 consecutive failures, the endpoint is automatically disabled

Webhook deliveries are not retried. If your server is temporarily down, those events will be missed. For critical workflows, use the Messages API to backfill any gaps.

When an endpoint is auto-disabled, you will see the failure count and last error message in Settings > Webhooks. Fix the issue, then re-enable the endpoint to reset the counter.


Best Practices

  • Respond quickly. Return 200 immediately and process the event asynchronously. The 5-second timeout is strict.
  • Use a queue. Push incoming webhooks to a job queue (Redis, SQS, etc.) rather than doing heavy processing inline.
  • Verify every request. Always check the X-Xmit-Signature header to ensure the payload is authentic.
  • Handle duplicates. In rare cases, the same event may be delivered more than once. Use messageId + event type as a deduplication key.
  • Monitor failures. Check the failure count in your dashboard periodically. If an endpoint is auto-disabled, investigate and re-enable it.