MiN8T
Home

Using Webhooks: Automate Your Email Workflow

Sarah Chen
Sarah Chen
Email Strategy Lead at MiN8T

Webhooks let MiN8T push real-time notifications to your systems whenever something happens in your account -- a template is created, an export completes, an ESP connection changes. Instead of polling our API to check for updates, your endpoint receives an HTTP POST the moment an event occurs. This guide covers everything you need to set up, secure, and maintain webhook integrations with MiN8T.

7
Event categories
SHA256
HMAC signatures
5
Retry attempts
REST
Management API

1 Overview

Webhooks are the connective tissue between MiN8T and the rest of your technology stack. They enable workflows that would otherwise require manual intervention or custom polling scripts.

Common use cases:

Webhooks are available on all MiN8T plans. There is no limit to the number of webhook subscriptions you can create, and each subscription can listen to one or more event types.


2 Available Events

MiN8T currently supports seven event types across three categories: template lifecycle, export activity, and ESP connection management.

EventTriggerPayload Summary
template.createdA new email template is created (from scratch, duplicated, or imported)Template ID, name, creator email, creation method
template.updatedAn existing template's content or metadata is modified and savedTemplate ID, name, changed fields, editor email, version number
template.deletedA template is moved to trash or permanently deletedTemplate ID, name, deletion type (soft/hard), deleted by
export.completedAn email is exported to an ESP or webhook endpointTemplate ID, design name, HTML, JSON, export target, exporter email
esp.connectedA new ESP integration is authenticated and activatedIntegration ID, ESP name, connection method, connected by
esp.disconnectedAn ESP integration is removed or its credentials expireIntegration ID, ESP name, disconnect reason, disconnected by
esp.data.syncedData from an ESP is synchronized (lists, segments, templates pulled)Integration ID, ESP name, sync type, record counts
i

Event filtering: When creating a webhook, you select which events it receives. A webhook subscribed only to export.completed will never receive template or ESP events. Subscribe to multiple events on a single webhook, or create separate webhooks for different event types -- whichever fits your architecture.


3 Getting Started

Before creating your first webhook, you need two things:

  1. A MiN8T account with access to the Developer Tools settings (available to account owners and team members with the "developer" or "owner" role)
  2. A webhook endpoint URL -- an HTTPS URL that can receive POST requests and respond with a 2xx status code. This can be a custom server, a serverless function (AWS Lambda, Cloudflare Workers, Vercel), or a third-party automation platform (Make.com, Zapier).
!

HTTPS required. MiN8T only delivers webhooks to HTTPS endpoints. Plain HTTP URLs will be rejected when creating the subscription. This ensures payload data is encrypted in transit.

To access webhook management:

  1. Log in to your MiN8T account
  2. Click the gear icon or navigate to Settings
  3. Select Developer Tools from the sidebar
  4. Click Webhooks

This page shows all your active webhook subscriptions, their delivery status, and recent delivery logs. From here you can create new webhooks, edit existing ones, view delivery history, and manually trigger test pings.


4 Creating a Webhook

Click Create Webhook on the webhooks management page. You will be asked for four pieces of information:

  1. Name -- a descriptive label for your reference (e.g., "Slack notifications" or "CMS sync endpoint"). This is not sent in the webhook payload.
  2. URL -- the HTTPS endpoint that will receive POST requests. MiN8T validates this URL is reachable before saving.
  3. Events -- select one or more event types from the list. You can change the selected events later without regenerating the signing secret.
  4. Active -- toggle to enable or disable delivery. Disabled webhooks retain their configuration but do not receive events.

After clicking Create, MiN8T generates a signing secret and displays it once. Copy this secret and store it securely -- you will need it to verify webhook signatures. The secret cannot be retrieved after this screen. If you lose it, you must rotate the secret (which generates a new one and invalidates the old).

Testing with a ping

After creation, click Send Ping to send a test event to your endpoint. The ping payload looks like this:

JSON
{
  "event": "ping",
  "timestamp": "2026-04-03T10:00:00.000Z",
  "data": {
    "webhookId": "wh_abc123def456",
    "message": "Webhook is configured correctly."
  }
}

If your endpoint returns a 2xx response, the ping is marked as successful in the delivery log. If it fails, MiN8T shows the HTTP status code and response body to help you debug.

REST API for webhook management

You can also manage webhooks programmatically through the REST API:

cURL
# List all webhooks
curl -H "MiN8T-Api-Auth: YOUR_API_KEY" \
  https://api.min8t.com/v1/webhooks

# Create a webhook
curl -X POST -H "MiN8T-Api-Auth: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Production sync",
    "url": "https://api.yourapp.com/webhooks/min8t",
    "events": ["template.updated", "export.completed"],
    "active": true
  }' \
  https://api.min8t.com/v1/webhooks

# Delete a webhook
curl -X DELETE -H "MiN8T-Api-Auth: YOUR_API_KEY" \
  https://api.min8t.com/v1/webhooks/wh_abc123def456

5 Payload Format

Every webhook delivery is a POST request with a JSON body. The top-level structure is consistent across all event types:

JSON
{
  "event": "template.updated",
  "timestamp": "2026-04-03T14:30:00.000Z",
  "data": {
    "templateId": "tpl_a1b2c3d4e5f6",
    "name": "April Newsletter",
    "updatedBy": "designer@company.com",
    "version": 12,
    "changedFields": ["html", "subject"],
    "subject": "Your April roundup is here",
    "preheader": "New features, tips, and community highlights"
  }
}

The event field tells you which event occurred. The timestamp is always ISO 8601 in UTC. The data object varies by event type -- see the table in section 2 for a summary of what each event includes.

Headers included with every delivery

HeaderValue
Content-Typeapplication/json
User-AgentMiN8T-Webhooks/1.0
X-MiN8T-EventEvent name (e.g., template.updated)
X-MiN8T-DeliveryUnique delivery ID (UUID) for deduplication
X-MiN8T-SignatureHMAC-SHA256 signature of the request body

Deduplication: Use the X-MiN8T-Delivery header as an idempotency key. If a delivery is retried (see section 7), the same delivery ID is sent. Store processed delivery IDs and skip duplicates to ensure your handler is idempotent.


6 Signature Verification

Every webhook delivery includes an X-MiN8T-Signature header containing an HMAC-SHA256 hash of the raw request body, computed using your webhook's signing secret. You should always verify this signature before processing the payload.

Why verification matters:

Node.js

Node.js
const crypto = require('crypto');

function verifyWebhook(payload, signature, secret) {
  const expected = 'sha256=' + crypto
    .createHmac('sha256', secret)
    .update(payload, 'utf8')
    .digest('hex');

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

// Express example (use raw body for signature verification)
app.post('/webhooks/min8t', express.raw({ type: 'application/json' }), (req, res) => {
  const sig = req.headers['x-min8t-signature'];
  if (!verifyWebhook(req.body, sig, process.env.WEBHOOK_SECRET)) {
    return res.status(401).json({ error: 'Invalid signature' });
  }

  const event = JSON.parse(req.body);
  console.log(`Received ${event.event} at ${event.timestamp}`);

  // Respond quickly, process asynchronously
  res.status(200).json({ received: true });
  processEventAsync(event);
});

Python

Python
import hmac
import hashlib

def verify_webhook(payload: bytes, signature: str, secret: str) -> bool:
    expected = 'sha256=' + hmac.new(
        secret.encode('utf-8'),
        payload,
        hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(signature, expected)

# Flask example
@app.route('/webhooks/min8t', methods=['POST'])
def handle_webhook():
    sig = request.headers.get('X-MiN8T-Signature', '')
    if not verify_webhook(request.data, sig, WEBHOOK_SECRET):
        return jsonify({'error': 'Invalid signature'}), 401

    event = request.get_json()
    print(f"Received {event['event']} at {event['timestamp']}")

    # Queue for async processing
    tamin8t_sk_queue.enqueue(process_event, event)
    return jsonify({'received': True}), 200

Go

Go
package main

import (
    "crypto/hmac"
    "crypto/sha256"
    "encoding/hex"
    "fmt"
    "io"
    "net/http"
)

func verifyWebhook(payload []byte, signature, secret string) bool {
    mac := hmac.New(sha256.New, []byte(secret))
    mac.Write(payload)
    expected := "sha256=" + hex.EncodeToString(mac.Sum(nil))
    return hmac.Equal([]byte(signature), []byte(expected))
}

func webhookHandler(w http.ResponseWriter, r *http.Request) {
    body, _ := io.ReadAll(r.Body)
    sig := r.Header.Get("X-MiN8T-Signature")

    if !verifyWebhook(body, sig, webhookSecret) {
        http.Error(w, `{"error":"Invalid signature"}`, http.StatusUnauthorized)
        return
    }

    fmt.Fprintf(w, `{"received":true}`)
    go processEvent(body) // async processing
}
!

Use the raw body. Compute the signature from the raw request bytes, not from a parsed-and-re-serialized JSON object. JSON serialization can reorder keys, change whitespace, or alter number formatting, which would produce a different hash. In Express, use express.raw(). In Flask, use request.data. In Go, use io.ReadAll(r.Body).


7 Retry Policy

If your endpoint does not return a 2xx status code within 10 seconds, MiN8T considers the delivery failed and schedules a retry. The retry schedule uses exponential backoff:

AttemptDelay After FailureCumulative Wait
1st retry1 minute1 minute
2nd retry5 minutes6 minutes
3rd retry30 minutes36 minutes
4th retry2 hours2 hours 36 minutes
5th retry (final)12 hours14 hours 36 minutes

After the 5th retry fails, the delivery is marked as permanently failed in the delivery log. MiN8T does not retry again automatically. You can manually re-send any failed delivery from the webhook management UI.

What counts as success or failure

i

Automatic disabling: If a webhook fails on every delivery for 7 consecutive days, MiN8T automatically disables it and sends an email notification to the account owner. Re-enable it from the webhooks management page once your endpoint is back online.


8 Best Practices

Building a reliable webhook consumer is straightforward if you follow these guidelines:

Respond quickly

Return a 2xx response within 5 seconds. Do not process the event synchronously in your request handler. Accept the delivery, enqueue the event for background processing, and respond immediately. This prevents timeouts and retries for events that simply take a while to process.

Node.js
// Good: respond first, process later
app.post('/webhooks/min8t', (req, res) => {
  res.status(200).json({ received: true });
  queue.add('process-webhook', req.body);
});

// Bad: process synchronously (risks timeout)
app.post('/webhooks/min8t', async (req, res) => {
  await syncToDatabase(req.body);      // 3 seconds
  await notifySlack(req.body);         // 2 seconds
  await updateDashboard(req.body);     // 4 seconds -- timeout!
  res.status(200).json({ received: true });
});

Handle duplicates with idempotency

Due to retries and network conditions, your endpoint may receive the same event more than once. Use the X-MiN8T-Delivery header as an idempotency key:

Always verify signatures

Never skip signature verification, even in development. It takes a few lines of code (see section 6) and protects you from forged payloads. In development, use a tool like ngrok to expose your local server with HTTPS, and use the real signing secret.

Monitor delivery health

Check the webhook delivery log in Settings → Developer Tools → Webhooks regularly. Look for patterns of failures, which might indicate:

Use separate webhooks for different concerns

Rather than routing all events to a single endpoint and parsing them internally, consider creating separate webhooks for different systems. For example: one webhook for your CMS sync (subscribes to template.updated), another for your Slack notifications (subscribes to export.completed), and a third for your audit log (subscribes to all events). This keeps each handler simple and reduces blast radius if one endpoint has issues.

Automate your email workflow

Set up your first webhook in under a minute and connect MiN8T to your stack.

Get started free