Webhooks
Webhooks send an HTTP POST to your server when something happens to your email, so your own systems stay in sync.
Events
| Event | Sent when |
|---|---|
unsub |
A contact unsubscribes |
complaint |
A contact marks an email as spam |
bounce |
An email hard-bounces |
open |
A contact opens an email |
click |
A contact clicks a link |
open_click |
A contact opens or clicks |
unsub_complaint |
A contact unsubscribes or complains |
Create a webhook
In the portal, open Integrations → Webhooks and add the URL that should receive events, then choose the event. Use the Test Webhook dialog to see example data for each event type.
With the API:
curl -s -X POST https://mailhoor.com/api/webhooks \
-H "X-Auth-APIKey: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name": "Unsubscribes to CRM", "target_url": "https://yourapp.com/hooks/mailhoor", "event": "unsub"}'
The response is 201 with the new webhook. A 409 means the same webhook already exists.
Receiving events
- Your endpoint must use HTTPS and answer with HTTP
200quickly. Do slow work after you respond. - The same event can arrive more than once. Make your handler safe to run twice.
- Treat the data as untrusted input: validate it before you write to your database.
Verify the signature
Every webhook carries two headers: X-MailHoor-Timestamp (Unix seconds) and X-MailHoor-Signature (sha256= + HMAC-SHA256 of the timestamp, a dot and the raw request body). Find your signing secret under Integrations → Webhooks → Signing secret.
import hashlib, hmac, time
def verify(secret: str, timestamp: str, signature: str, raw_body: bytes) -> bool:
if abs(time.time() - int(timestamp)) > 300:
return False
expected = "sha256=" + hmac.new(secret.encode(), timestamp.encode() + b"." + raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, signature)
Use the raw body exactly as received, before parsing JSON. Reject requests older than 5 minutes. If you replace the secret, update your server right away.
Common uses
- Mark unsubscribed contacts in your CRM (
unsub,complaint). - Clean bounced addresses from your own database (
bounce). - Score leads by engagement (
open_click).