ConvertNow fires outbound webhooks for every transactional email send. Register HTTPS endpoints from Email API → Webhooks.
Events
Event | When it fires |
email.sent | The mail server accepted the message |
email.failed | Delivery failed — the mail server rejected the message |
Registering an endpoint
1. Go to Email API → Webhooks and click Add Endpoint
2. Enter your HTTPS URL
3. Select the events to subscribe to
4. Click Save Endpoint
5. Copy the signing secret immediately — it is only shown once
Payload shapes
email.sent
{
“event”: “email.sent”,
“created_at”: “2026-07-10T12:00:00.000Z”,
“data”: {
“message_id”: “msg_abc123”,
“to”: [“[email protected]”],
“from”: “[email protected]”,
“subject”: “Welcome”,
“status”: “sent”
}
}
email.failed
{
“event”: “email.failed”,
“created_at”: “2026-07-10T12:00:00.000Z”,
“data”: {
“message_id”: null,
“to”: [“[email protected]”],
“from”: “[email protected]”,
“subject”: “Welcome”,
“status”: “failed”,
“error”: “550 user does not exist”
}
}
Signature verification
Every request includes an X-ConvertNow-Signature header: sha256=<hmac-hex>. Always verify before processing.
Node.js (Express)
import crypto from ‘crypto’;
import express from ‘express’;
function verifyWebhook(rawBody, signature, secret) {
const expected = ‘sha256=’ + crypto
.createHmac(‘sha256’, secret)
.update(rawBody)
.digest(‘hex’);
return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
}
app.post(‘/webhooks/email’, express.raw({ type: ‘application/json’ }), (req, res) => {
const sig = req.headers[‘x-convertnow-signature’];
if (!sig || !verifyWebhook(req.body, sig, process.env.CONVERTNOW_WEBHOOK_SECRET)) {
return res.status(401).send(‘Invalid signature’);
}
const event = JSON.parse(req.body);
if (event.event === ’email.failed’) {
console.error(‘Delivery failed:’, event.data.to, event.data.error);
}
res.sendStatus(200);
});
Python (Flask)
import hmac, hashlib, os
from flask import Flask, request, abort
app = Flask(__name__)
SECRET = os.environ[‘CONVERTNOW_WEBHOOK_SECRET’].encode()
@app.route(‘/webhooks/email’, methods=[‘POST’])
def webhook():
sig = request.headers.get(‘X-ConvertNow-Signature’, ”)
expected = ‘sha256=’ + hmac.new(SECRET, request.data, hashlib.sha256).hexdigest()
if not hmac.compare_digest(sig, expected):
abort(401)
event = request.get_json(force=True)
return ”, 200