HomeConvertNow KnowledgebaseWebhook Documentation

Webhook Documentation

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

This is a staging environment