Webhook Integration: A Complete Guide | MailParse

Learn about Webhook Integration. Real-time email delivery via webhooks with retry logic and payload signing. Expert guide for developers.

Why webhook integration matters for real-time email delivery

Webhooks power real-time integrations by pushing events to your application the moment they happen. For email-centric SaaS products, a solid webhook-integration means inbound messages arrive within seconds, are parsed into structured JSON, and are available to your business logic without polling. This is faster, cheaper, and more reliable than periodic fetch jobs.

If your search led you here with a confusing query like "[object Object]", you are probably working on a webhook-integration where raw objects or MIME bodies need to be turned into actionable data. This guide explains the concepts, shows working code, and highlights best practices for secure, resilient delivery.

Services like MailParse can deliver parsed inbound email to your endpoint via webhook with retry logic and payload signing. Even if you roll your own stack, the patterns below provide a robust blueprint.

Core concepts of webhook integration for email workflows

Event flow overview

  • Your system allocates a stable HTTPS endpoint that accepts POST requests.
  • The email processing service receives a message, parses MIME into JSON, then POSTs the payload to your endpoint.
  • Your endpoint verifies the request signature, enqueues work, responds with a 2xx quickly, and processes the job asynchronously.
  • If delivery fails, the sender retries with backoff until success or a maximum attempt count.

HTTP contracts and status codes

  • Method: POST
  • Return 2xx for successful receipt - 204 No Content is common and fast.
  • Return 4xx for permanent errors like bad signature or unsupported version. The sender should stop retrying.
  • Return 5xx for transient failures like timeouts or database outages. The sender should retry with backoff.

Payload structure example

While formats differ by provider, robust email webhooks include fields for sender, recipients, subject, text and HTML bodies, headers, attachments, and verification metadata. A typical JSON payload might look like this:

{
  "event": "inbound_email",
  "id": "evt_7b3d9e",
  "message_id": "msg_f2c4aa",
  "timestamp": 1710000000,
  "from": { "email": "alice@example.com", "name": "Alice" },
  "to": [
    { "email": "support@acme.test", "name": "Support" }
  ],
  "cc": [],
  "subject": "Invoice for March",
  "text": "Hi team,\nPlease find the invoice attached.",
  "html": "<p>Hi team,</p><p>Please find the invoice attached.</p>",
  "headers": {
    "Message-Id": "<abc@mx.example.com>",
    "In-Reply-To": "<thread-123@acme.test>"
  },
  "attachments": [
    {
      "filename": "invoice.pdf",
      "content_type": "application/pdf",
      "size": 134560,
      "url": "https://files.example.com/att/att_12ab34",
      "sha256": "3b0f...f932"
    }
  ],
  "spam": { "score": 0.3, "flagged": false },
  "dkim": { "verified": true },
  "spf": { "pass": true },
  "tenant_id": "acme-123",
  "meta": { "version": 1 },
  "signature": {
    "version": "v1",
    "algorithm": "HMAC-SHA256",
    "timestamp": "1710000000",
    "nonce": "n_08a2f9",
    "signature": "e4b1...9d7c"
  }
}

Note the presence of a signature object. Always verify it before processing.

Signature verification

Most webhook providers sign requests with a shared secret using HMAC. A common scheme uses a base string like {timestamp}.{rawBody}. Your endpoint must compute the HMAC with the secret, compare against the provided signature in constant time, and reject if invalid or stale.

Constant-time HMAC verification - Node.js

import crypto from "crypto";
import express from "express";

const app = express();
// Use raw body so you can verify before JSON parsing
app.use("/webhooks/email", express.raw({ type: "application/json", limit: "10mb" }));

const SIGNING_SECRET = process.env.SIGNING_SECRET;

function safeEqual(a, b) {
  const ab = Buffer.from(a, "utf8");
  const bb = Buffer.from(b, "utf8");
  if (ab.length !== bb.length) return false;
  return crypto.timingSafeEqual(ab, bb);
}

app.post("/webhooks/email", (req, res) => {
  const timestamp = req.header("X-Timestamp");
  const signature = req.header("X-Signature");
  const now = Math.floor(Date.now() / 1000);

  // Reject if clock skew is too large, for example > 5 minutes
  if (!timestamp || Math.abs(now - Number(timestamp)) > 300) {
    return res.status(400).send("stale timestamp");
  }

  const raw = req.body.toString("utf8");
  const base = `${timestamp}.${raw}`;
  const digest = crypto.createHmac("sha256", SIGNING_SECRET).update(base).digest("hex");

  if (!signature || !safeEqual(digest, signature)) {
    return res.status(400).send("bad signature");
  }

  // Parse after verifying
  const event = JSON.parse(raw);

  // Enqueue for async processing
  queue.add("inbound_email", event);

  // Acknowledge quickly
  return res.status(204).send();
});

app.listen(3000, () => console.log("listening on :3000"));

Constant-time HMAC verification - Python (Flask)

import hmac, hashlib, time
from flask import Flask, request, abort

app = Flask(__name__)
SIGNING_SECRET = b"your_shared_secret"

def safe_equal(a: str, b: str) -> bool:
    return hmac.compare_digest(a, b)

@app.post("/webhooks/email")
def webhook():
    ts = request.headers.get("X-Timestamp")
    sig = request.headers.get("X-Signature")
    if not ts or not sig:
        abort(400)

    now = int(time.time())
    if abs(now - int(ts)) > 300:
        abort(400)

    raw = request.get_data(cache=False)
    base = f"{ts}.{raw.decode('utf-8')}".encode("utf-8")
    digest = hmac.new(SIGNING_SECRET, base, hashlib.sha256).hexdigest()

    if not safe_equal(digest, sig):
        abort(400)

    event = request.get_json()
    enqueue("inbound_email", event)
    return ("", 204)

Practical webhook-integration patterns for real-time email

Turn inbound email into tickets

Route messages sent to support addresses to your ticketing system. Use recipient mapping to select queues, extract customer IDs from the subject or headers, and attach files to the ticket. Verify DKIM and SPF fields before auto-assigning to avoid spam tickets.

Email commands for workflow automation

Enable simple commands in the email body like /close or /assign @alex. Parse the plain text part first, then fall back to HTML to improve accuracy. Only accept commands from allowlisted domains or users you trust.

Archival and search indexing

Persist the normalized JSON, index text with your search engine, and place attachments in object storage. Store a content hash to deduplicate repeated deliveries. Redact sensitive data before indexing.

Quick-start endpoint with fast ack and background jobs

This Node.js example verifies the signature, acknowledges quickly, and uses a queue for asynchronous processing. Replace queue.add with your preferred job runner.

import express from "express";
import crypto from "crypto";

const app = express();
app.use("/webhooks/email", express.raw({ type: "application/json", limit: "10mb" }));
const SECRET = process.env.SIGNING_SECRET;

function verify(req) {
  const ts = req.header("X-Timestamp");
  const sig = req.header("X-Signature");
  if (!ts || !sig) return false;
  const raw = req.body.toString("utf8");
  const base = `${ts}.${raw}`;
  const digest = crypto.createHmac("sha256", SECRET).update(base).digest("hex");
  if (Math.abs(Math.floor(Date.now()/1000) - Number(ts)) > 300) return false;
  return crypto.timingSafeEqual(Buffer.from(digest), Buffer.from(sig));
}

app.post("/webhooks/email", (req, res) => {
  if (!verify(req)) return res.status(400).send("bad signature");
  const event = JSON.parse(req.body.toString("utf8"));

  queue.add("email", event, { removeOnComplete: true });

  // Respond fast - under 1s - to avoid retries
  return res.status(204).send();
});

app.listen(8080);

Local testing and replay

  • Expose your local server with a tunnel like ngrok http 8080 or cloudflared tunnel.
  • Configure your webhook endpoint in your provider with the public URL.
  • Use replay tools, or trigger real inbound messages to see payloads in your logs.

If you need a foundation on MIME parsing and normalized structures, see Email Parsing API: A Complete Guide | MailParse. For end-to-end data flow patterns, consult Inbound Email Processing: A Complete Guide | MailParse.

Best practices for secure, resilient webhook delivery

Security first

  • Verify HMAC signatures on every request using the raw body.
  • Use HTTPS with modern TLS. Redirect HTTP to HTTPS or reject outright.
  • Limit accepted content types to application/json.
  • Validate schema and allowed fields. Reject unknown event types gracefully.
  • Consider IP allowlists only if your provider publishes stable ranges. Signature verification is more reliable.
  • Encrypt stored payloads and attachments at rest. Redact or tokenize PII before logging.

Resilience and scalability

  • Ack fast - under 1 second - to prevent unnecessary retries.
  • Process asynchronously via a job queue. Keep webhook handlers stateless.
  • Implement idempotency. Deduplicate by event.id or hash of stable fields.
  • Handle retries with exponential backoff and jitter on the sender side, and be tolerant of duplicates on the receiver side.
  • Use timeouts of 3 to 10 seconds for the sender and keep your handler budget well below that.
  • Monitor with metrics: rate, latency, success ratio, retry counts, and DLQ backlog.

Data management

  • Stream large attachments to storage rather than loading into memory.
  • Scan attachments for malware before making them available to users.
  • Normalize encodings to UTF-8. Preserve original headers for forensic use.
  • Version your payload schema. Include a meta.version field and support at least one older version for safe rollouts.

Operational excellence

  • Include a trace ID in logs that links your 2xx ack with the async job.
  • Emit structured logs. Avoid logging raw content bodies that might include secrets.
  • Set SLOs for delivery and processing time, for example 99.9 percent of events acknowledged in under 1 second.
  • Run chaos tests that simulate network drops and TLS misconfigurations.

Common webhook pitfalls and how to fix them

Duplicate deliveries

Cause: sender retries on timeouts or 5xx, or your handler returns 2xx after partial failure. Fix: make processing idempotent by using a unique event ID, a message hash, or a database constraint. Store a "processed" record keyed by event.id and return 204 even if the event was seen.

Out-of-order events

Cause: network conditions or parallel retries. Fix: do not assume order. Design handlers to be commutative when possible. For stateful workflows, gate updates on a timestamp or sequence number and ignore stale updates.

Large payloads and 413s

Cause: oversized JSON or attachments. Fix: increase server limits thoughtfully, for example 10 MB for JSON. Prefer pre-signed URLs for attachments and stream downloads. Validate file types and sizes before fetching.

Parsing HTML-only emails

Cause: some senders omit plain text. Fix: extract text from HTML with a sanitizer and fallback strategy. Track a "text_quality" flag to let downstream consumers adjust behavior.

Signature verification failures

Cause: using parsed JSON rather than the raw byte stream for HMAC, incorrect secret, or significant clock skew. Fix: verify against the exact raw body, rotate secrets carefully, and synchronize clocks with NTP. Consider rejecting requests with a timestamp older than 5 minutes.

Mixed success due to long processing

Cause: heavy work inside the webhook handler leads to timeouts and retries. Fix: move heavy tasks to a queue, decompose work into smaller jobs, and acknowledge quickly. Ensure idempotent consumer behavior when retries occur.

HTTP redirects and TLS quirks

Cause: redirecting from http to https or changing hosts can break deliveries since many senders do not follow redirects. Fix: provide a stable https URL. Renew TLS certificates early and test after any infrastructure change.

Conclusion

Real-time webhook integration turns inbound email into immediate, actionable events. Secure your endpoint with HMAC signatures, acknowledge quickly, process asynchronously, and build idempotent handlers so retries are safe. The patterns in this topic landing guide will help you ship a reliable pipeline that scales with traffic.

If you prefer a managed service, MailParse provides instant inboxes, structured JSON for MIME, and delivers via webhooks with signing and retry logic. Start with a local tunnel, wire up a test inbox, and iterate until your logs show fast 2xx responses and clean job processing.

FAQ

What is the difference between webhooks and polling?

Polling asks the server for new data on a schedule. Webhooks push data to you as soon as events happen. For email delivery, webhooks reduce latency, lower infrastructure costs, and simplify logic since you handle each event once with retries handled upstream.

How should I handle retries and idempotency?

Always assume duplicates. Store a unique event key, for example event.id, and use database constraints or a cache to ignore reprocessing. Keep webhook handlers fast and stateless, return 2xx once the event is queued, and let the sender retry only on 5xx or timeouts.

How do I verify webhook signatures?

Compute an HMAC of {timestamp}.{rawBody} with your shared secret and compare it in constant time with the signature header. Reject missing or stale timestamps and any request that fails comparison. Perform verification before JSON parsing to avoid tampering issues.

Can I receive and store attachments safely?

Yes. Accept metadata in the JSON and download attachments via secure URLs. Stream to storage, verify checksums, scan for malware, and limit file types. Avoid loading large files into memory. Record content hashes to deduplicate repeats.

What response should my webhook return?

Return 204 No Content for success as soon as the event is enqueued. Use 4xx for permanent problems like bad signatures and 5xx for transient errors. Keep handler latency under a second so upstream systems do not retry unnecessarily.

Ready to get started?

Start parsing inbound emails with MailParse today.

Get Started Free