An email leaves your application and you receive an API response confirming it was accepted for delivery. But what happens after that? Did it reach the inbox? Did it bounce? Did the recipient mark it as spam? Without webhooks, you have no way to know. Webhooks push delivery event data to your application in real time — and handling them correctly is essential for maintaining list quality and sender reputation.
What Are Email Webhooks?
A webhook is an HTTP callback. When a delivery event occurs — a bounce, a click, an unsubscribe — NexusProMail makes an HTTP POST request to your registered endpoint with a JSON payload describing the event. Your application receives the data, processes it and responds with a 200 status code to acknowledge receipt.
Webhooks replace the need to poll the API for delivery status. For high-volume sending, polling is impractical. Webhooks push the data as events occur.
Registering a Webhook Endpoint
Register your webhook endpoint via the API:
curl -X POST https://app.nexuspromail.com/api/v1/api/webhooks \
-H 'Authorization: Bearer YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-d '{
"url": "https://yourapp.com/webhooks/email",
"events": ["bounced.hard", "bounced.soft", "complained", "unsubscribed", "delivered", "opened", "clicked"]
}'
The response includes your webhook secret. Store it as an environment variable (WEBHOOK_SECRET) — you will use it to verify incoming payloads.
Webhook Payload Structure
Every webhook event follows a consistent structure:
{
"id": "evt_01hx...",
"type": "bounced.hard",
"created_at": "2026-05-13T10:24:31Z",
"data": {
"message_id": "msg_01hx...",
"email": "recipient@example.com",
"timestamp": "2026-05-13T10:24:28Z",
"bounce_code": "550",
"bounce_reason": "5.1.1 The email account that you tried to reach does not exist"
}
}
Key fields: type identifies the event; data.message_id correlates to the original send; data.email is the affected recipient address.
Verifying Webhook Signatures
Every webhook request includes an X-NexusProMail-Signature header containing an HMAC-SHA256 signature of the raw request body, computed using your webhook secret. Always verify this before processing the payload. An unverified webhook could be forged.
const crypto = require('crypto')
function verifyWebhookSignature(rawBody, receivedSignature, secret) {
const computed = crypto
.createHmac('sha256', secret)
.update(rawBody, 'utf8')
.digest('hex')
// Use timingSafeEqual to prevent timing-based attacks
if (computed.length !== receivedSignature.length) return false
return crypto.timingSafeEqual(
Buffer.from(computed, 'hex'),
Buffer.from(receivedSignature, 'hex')
)
}
Important: read the raw request body for signature computation. Do not use the parsed JSON — any whitespace difference between the raw body and re-serialised JSON will break signature verification.
How to Handle Each Event Type
bounced.hard
A hard bounce means the address does not exist or has permanently rejected the message. Action: suppress the address immediately in your own database. Do not attempt to resend. Continuing to send to hard-bounced addresses will damage your sender reputation and will be rejected by NexusProMail's suppression enforcement anyway.
case 'bounced.hard':
await db.execute(
'UPDATE users SET email_status = $1, email_bounced_at = NOW() WHERE email = $2',
['hard_bounced', event.data.email]
)
// Also remove from any active marketing lists
await db.execute(
'DELETE FROM mailing_list_members WHERE email = $1',
[event.data.email]
)
break
bounced.soft
A soft bounce is a temporary failure. NexusProMail retries automatically. Your webhook receives a soft bounce notification but you generally do not need to act immediately. Track consecutive soft bounces per address — if the same address generates 3+ soft bounces without a successful delivery, treat it as a potential hard bounce and investigate.
complained
A spam complaint is the most damaging deliverability event. Suppress the address immediately and do not contact them again for any marketing purpose. Even transactional email to a complainant should be reviewed — if the person considered your email spam, continued contact may generate more complaints.
unsubscribed
The recipient clicked the unsubscribe link. Update their subscription status in your database. Do not send further marketing email. NexusProMail enforces suppression automatically, but keeping your own database current prevents incorrect status in your CRM or customer records.
delivered, opened, clicked
Positive engagement events. Use these to update last-active timestamps, trigger follow-up sequences and feed engagement scoring for segmentation. For segmentation purposes, opened and clicked are more valuable signals than delivered.
Webhook Reliability Patterns
Respond with 200 immediately
Return a 200 status code as quickly as possible — before you process the event. Use a queue to handle processing asynchronously. If your webhook endpoint is slow or returns a non-200 response, NexusProMail will retry delivery, which can cause duplicate event processing.
app.post('/webhooks/email', express.raw({ type: 'application/json' }), async (req, res) => {
// Verify signature first
const sig = req.headers['x-nexuspromail-signature']
if (!verifyWebhookSignature(req.body, sig, process.env.WEBHOOK_SECRET)) {
return res.status(401).json({ error: 'Invalid signature' })
}
// Acknowledge immediately
res.sendStatus(200)
// Process asynchronously
const event = JSON.parse(req.body)
await eventQueue.push(event) // or setImmediate(() => processEvent(event))
})
Handle duplicates idempotently
Webhook events can be delivered more than once. Use event.id as an idempotency key — check if you have already processed this event ID before acting on it. Store processed event IDs in a cache or database with a TTL of 24–48 hours.
Log every event
Log the full event payload before processing. If your processing logic has a bug, the log gives you the raw data to replay events once the bug is fixed.