Email Webhooks
Email Status Event Webhook Configuration
This section explains how users can configure webhooks in the application and verify signatures using the provided signature verification secret for enhanced security.
Adding a Webhook URL
Users can specify a webhook URL in the application where events will be sent. The webhook URL should be a publicly accessible endpoint capable of handling HTTP POST requests.
Events are sent only for emails sent through the API with a sender on the Easify Email Gateway. Emails sent with any other sender (for example your own SMTP server) do not trigger webhook events.
Steps to Add a Webhook
- Go to Settings -> API Settings -> Email -> Status Update.

Webhook Event Structure
When an event is triggered, the application will send a POST request to the configured webhook URL with the following headers and body:
Headers
- X-Easify-Signature: A Base64-encoded signature of the payload, generated using the verification secret.
- X-Easify-Timestamp: The UNIX timestamp of the request.
Payload
The POST body contains the event data. Single-recipient and multi-recipient emails use the same webhook URL and the same events, but each identifies the email with a different field:
| Email sent with | ID field in the event | What it is |
|---|---|---|
| Send Single-Recipient Email | email_item_id | The email_id returned when you sent that email, the same value Single-Recipient Email Status uses |
| Send Multi-Recipient Email | email_id | The email_id returned for the whole send, the same value Multi-Recipient Email Status uses. Every recipient shares it, so use it together with email to tell recipients apart. |
[
{
"additional_data" : "{'name':'John','id':'232'}",
"email": "example@test.com",
"email_item_id": "675bd3446c684a55a308****",
"timestamp": 1734081***,
"smtp-id": "<14c5d75ce93.dfd.64b469@ismtpd-***>",
"event": "processed",
"category": ["cat facts"],
"sg_event_id": "4mA9l_cHKMlUEEkEuZc3****",
"sg_message_id": "14c5d75ce93.dfd.64b469.filter0001.16648.5515E0B8****"
},
{
"additional_data" : "{'name':'John','id':'232'}",
"email": "example@test.com",
"email_item_id": "675bd3446c684a55a308****",
"timestamp": 1734081***,
"smtp-id": "<14c5d75ce93.dfd.64b469@ismtpd-***>",
"event": "deferred",
"category": ["cat facts"],
"sg_event_id": "eKRNAYmTi2jlSdymGKZs****",
"sg_message_id": "14c5d75ce93.dfd.64b469.filter0001.16648.5515E0B8****",
"response": "400 try again later",
"attempt": "5"
},
{
"additional_data" : "{'name':'John','id':'232'}",
"email": "example@test.com",
"email_item_id": "675bd3446c684a55a308****",
"timestamp": 1734081***,
"smtp-id": "<14c5d75ce93.dfd.64b469@ismtpd-***>",
"event": "delivered",
"category": ["cat facts"],
"sg_event_id": "GuGhSahRjAq7Ci2m_FF****",
"sg_message_id": "14c5d75ce93.dfd.64b469.filter0001.16648.5515E0B8****",
"response": "250 OK"
},
{
"additional_data" : "{'name':'John','id':'232'}",
"email": "example@test.com",
"email_item_id": "675bd3446c684a55a308****",
"timestamp": 1734081***,
"smtp-id": "<14c5d75ce93.dfd.64b469@ismtpd-***>",
"event": "open",
"category": ["cat facts"],
"sg_event_id": "ABygEH8H1JfhmJQnDEtz****",
"sg_message_id": "14c5d75ce93.dfd.64b469.filter0001.16648.5515E0B8****",
"useragent": "Mozilla/4.0 (compatible; MSIE 6.1; Windows XP; .NET CLR 1.1.4322; .NET CLR 2.0.50727)",
"ip": "255.255.255.***"
},
{
"additional_data" : "{'name':'John','id':'232'}",
"email": "example@test.com",
"email_item_id": "675bd3446c684a55a308****",
"timestamp": 1734081***,
"smtp-id": "<14c5d75ce93.dfd.64b469@ismtpd-***>",
"event": "click",
"category": ["cat facts"],
"sg_event_id": "RlVuHd4e6J1KX1c3UXtu****",
"sg_message_id": "14c5d75ce93.dfd.64b469.filter0001.16648.5515E0B8****",
"useragent": "Mozilla/4.0 (compatible; MSIE 6.1; Windows XP; .NET CLR 1.1.4322; .NET CLR 2.0.50727)",
"ip": "255.255.255.***",
"url": "http://www.sendgrid.com/"
},
{
"additional_data" : "{'name':'John','id':'232'}",
"email": "example@test.com",
"email_item_id": "675bd3446c684a55a308****",
"timestamp": 1734081***,
"smtp-id": "<14c5d75ce93.dfd.64b469@ismtpd-***>",
"event": "bounce",
"category": ["cat facts"],
"sg_event_id": "SMrxoGPPPugXFS_tLM0b****",
"sg_message_id": "14c5d75ce93.dfd.64b469.filter0001.16648.5515E0B8****",
"reason": "500 unknown recipient",
"status": "5.0.*"
},
{
"additional_data" : "{'name':'John','id':'232'}",
"email": "example@test.com",
"email_item_id": "675bd3446c684a55a308****",
"timestamp": 1734081***,
"smtp-id": "<14c5d75ce93.dfd.64b469@ismtpd-***>",
"event": "dropped",
"category": ["cat facts"],
"sg_event_id": "00udEyCl5KqOB8aviHUI****",
"sg_message_id": "14c5d75ce93.dfd.64b469.filter0001.16648.5515E0B8****",
"reason": "Bounced Address",
"status": "5.0.*"
},
{
"additional_data" : "{'name':'John','id':'232'}",
"email": "example@test.com",
"email_item_id": "675bd3446c684a55a308****",
"timestamp": 1734081***,
"smtp-id": "<14c5d75ce93.dfd.64b469@ismtpd-***>",
"event": "spamreport",
"category": ["cat facts"],
"sg_event_id": "cxwA33aN6INAhHxR4eEl****",
"sg_message_id": "14c5d75ce93.dfd.64b469.filter0001.16648.5515E0B8****"
},
{
"additional_data" : "{'name':'John','id':'232'}",
"email": "example@test.com",
"email_item_id": "675bd3446c684a55a308****",
"timestamp": 1734081***,
"smtp-id": "<14c5d75ce93.dfd.64b469@ismtpd-***>",
"event": "unsubscribe",
"category": ["cat facts"],
"sg_event_id": "6KQ3MWWC3jY5Fm9PgYxE****",
"sg_message_id": "14c5d75ce93.dfd.64b469.filter0001.16648.5515E0B8****"
},
{
"additional_data" : "{'name':'John','id':'232'}",
"email": "example@test.com",
"email_item_id": "675bd3446c684a55a308****",
"timestamp": 1734081***,
"smtp-id": "<14c5d75ce93.dfd.64b469@ismtpd-***>",
"event": "group_unsubscribe",
"category": ["cat facts"],
"sg_event_id": "E9VM-8dxczn2XE1nstn4****",
"sg_message_id": "14c5d75ce93.dfd.64b469.filter0001.16648.5515E0B8****",
"useragent": "Mozilla/4.0 (compatible; MSIE 6.1; Windows XP; .NET CLR 1.1.4322; .NET CLR 2.0.50727)",
"ip": "255.255.255.***",
"url": "http://www.sendgrid.com/",
"asm_group_id": 1*
},
{
"additional_data" : "{'name':'John','id':'232'}",
"email": "example@test.com",
"email_item_id": "675bd3446c684a55a308****",
"timestamp": 1734081***,
"smtp-id": "<14c5d75ce93.dfd.64b469@ismtpd-***>",
"event": "group_resubscribe",
"category": ["cat facts"],
"sg_event_id": "Q3zFzf5hk2ec_Ck5330R****",
"sg_message_id": "14c5d75ce93.dfd.64b469.filter0001.16648.5515E0B8****",
"useragent": "Mozilla/4.0 (compatible; MSIE 6.1; Windows XP; .NET CLR 1.1.4322; .NET CLR 2.0.50727)",
"ip": "255.255.255.***",
"url": "http://www.sendgrid.com/",
"asm_group_id": 1*
}
]
Steps for Validating the Signature
Extract Header and Payload Information
X-Easify-Signature and X-Easify-Timestamp from the request headers, and the raw JSON payload from the request body.
Read the body as raw bytes. Re-serialising a payload you have already parsed can reorder keys or change escaping, and the signature will no longer match.
Recreate the Signature
Compute an HMAC-SHA256 of the raw payload using your webhook secret. The digest is then written as a lowercase hex string, and it is that hex string which is Base64-encoded. Base64-encoding the raw digest bytes instead is the most common cause of a signature that never matches.
Compare the Signatures
Compare your computed value against the one received in the X-Easify-Signature header using a constant-time comparison.
Validate the Timestamp
To prevent replay attacks, ensure the timestamp in X-Easify-Timestamp is within an acceptable range of the current time (±15 minutes / 900 seconds).
Pass the raw body, both header values and your webhook secret into a helper like the one below, and reject the request unless it returns true.
php
// Get the raw request body.
// Use the original body for signature verification without modifying it.
$rawBody = file_get_contents('php://input');
// Read the signature and timestamp sent in the webhook headers.
$receivedSignature = $_SERVER['HTTP_X_EASIFY_SIGNATURE'] ?? '';
$receivedTimestamp = (int) ($_SERVER['HTTP_X_EASIFY_TIMESTAMP'] ?? 0);
// Your webhook signing secret from the Easify -> API Settings -> Webhooks -> Secret Key
$webhookSecret = getenv('EASIFY_WEBHOOK_SECRET');
// Reject requests older than 15 minutes to prevent replay attacks.
if (abs(time() - $receivedTimestamp) > 900) {
http_response_code(408);
exit;
}
// Generate the expected signature using the webhook secret.
$computedSignature = base64_encode(hash_hmac('sha256', $rawBody, $webhookSecret));
// Compare the received signature with the expected signature.
if (!hash_equals($computedSignature, $receivedSignature)) {
http_response_code(403);
exit;
}
// Signature is valid, now decode and handle the event.
$payload = json_decode($rawBody, true);
// ... handle the event ...
http_response_code(200);