Integrations
Webhooks
Cleanmails fires real-time webhook events when things happen in your outreach — emails sent, opened, clicked, replied, bounced, or leads unsubscribed. Use webhooks to sync with your CRM, trigger automations, or build custom dashboards.
Event Types
| Event | Fired When |
|---|---|
email.sent | An email is successfully delivered |
email.opened | Recipient opens the email (non-bot) |
email.clicked | Recipient clicks a tracked link (non-bot) |
email.replied | Recipient replies to the email |
email.bounced | Email bounces (hard or soft) |
lead.unsubscribed | Lead clicks unsubscribe |
campaign.started | Campaign status changes to running |
campaign.completed | All leads processed through all steps |
campaign.paused | Campaign is paused (manual or auto) |
lead.created | New lead added to a list |
lead.validated | Lead email validation completed |
Creating a Webhook
bash
curl -X POST http://YOUR_SERVER/v1/integrations/webhooks \
-H "Content-Type: application/json" \
-H "Cookie: auth_token=YOUR_SESSION" \
-d '{
"url": "https://your-app.com/webhook/cleanmails",
"secret": "your-signing-secret",
"events": ["email.sent", "email.replied", "email.bounced"]
}'Event filtering
Pass an empty events array or omit it to receive all events. Use specific event types to reduce noise.
Webhook Payload
POST to your URL
{
"event": "email.replied",
"workspace_id": 1,
"timestamp": "2026-05-12T14:30:00Z",
"data": {
"lead_id": 42,
"lead_email": "john@example.com",
"lead_name": "John Smith",
"lead_company": "Acme Inc",
"campaign_id": 5,
"campaign": "Q2 Outreach",
"sender_id": 3,
"sender": "alex@outreach.yourcompany.com",
"message_id": "<abc123@outreach.yourcompany.com>"
}
}Signature Verification
If you provide a secret when creating the webhook, every delivery includes an HMAC-SHA256 signature in the header:
text
X-Webhook-Signature: sha256=a1b2c3d4e5f6...Verify it server-side:
Node.js verification
const crypto = require('crypto');
function verifyWebhook(body, signature, secret) {
const expected = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(body)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}Retry & Auto-Pause
- Retries: 3 attempts with exponential backoff (2s → 4s → 8s)
- Auto-pause: After 10 consecutive failures, the webhook is automatically paused
- Re-enable: Update the webhook status to "active" to reset the failure counter
Testing
bash
# Send a test ping to your webhook
curl -X POST http://YOUR_SERVER/v1/integrations/webhooks/WEBHOOK_ID/test \
-H "Cookie: auth_token=YOUR_SESSION"