Webhooks are user-defined HTTP callbacks that are triggered by specific events in a server or application. They provide a powerful way for applications to communicate with each other in real-time. When an event occurs, the source site makes an HTTP request to the URL configured for the webhook. This allows developers to automate reactions to events such as pushing data to other services, updating external systems, or triggering complex workflows automatically. Unlike typical API calls that require polling for data at regular intervals, webhooks deliver data as it happens, greatly enhancing the efficiency and responsiveness of software ecosystems.
How do webhooks work in AhaSend?
At AhaSend, a webhook is a URL that you configure to integrate with our system, enabling you to receive real-time notifications about events linked to your transactional emails, changes to your suppression list, and DNS configuration errors affecting your domains. Webhooks empower you to monitor specific activities, such as the queuing, delivery, or bouncing of emails.
You can track a variety of events through webhooks. For instance, you can get alerts when an email is delivered, or if it fails to deliver, prompting real-time feedback to users to verify their email addresses. Additionally, you can monitor the status of your transactional messages—whether they are being queued, delivered, bounced (either hard or soft), deferred, or suppressed. Notifications can also be set up to inform you when a new suppression is added for a recipient following repeated delivery failures, or when the DNS configuration of your sending domains changes in a way that prevents AhaSend from sending emails from it.
Webhook requests are sent to the Endpoint URL using a POST request. Only responses with HTTP status codes between 200 and 299 are deemed successful. Failed requests are retried six times, up to 16 minutes and 21 seconds after the initial attempt. Receiving over 100 consecutive failures from the webhook endpoint URL will result in the webhook being disabled, and you will be notified via email about this change.
What events are available for AhaSend webhooks?
Here's a complete list of events you can receive using webhooks. You can choose which events to receive on your webhook URL when adding the webhook in your dashboard.
- Message Reception: An email has been received and queued for delivery.
- Message Delivered: An email has been successfully sent to the recipient.
- Message Deferred: An email has been delayed due to an issue with the receiving mail server, it will be retried later.
- Message Delivery Failed: An email cannot be delivered to the receiving mail server due to repeated failures. There will be no more retries.
- Message Bounced: We have received a bounce message by the receiving mail server. This can happen even for emails which had previously been successfully sent.
- Message Opened: The recipient has opened your email. This webhook event is only fired if you have Open Tracking enabled on your account for this specific email.
- Message Suppressed: No attempt was made to send the email because the recipient is suppressed.
- Suppression Created: A suppression was created and future emails to the recipient will be held back and not attempted. This happens when there have been multiple delivery errors associated with the email address.
- DNS Error Detected: We've detected an issue with the DNS configuration for any domain in your account. After this happens, all future requests for sending emails will be rejected until the DNS configuration is fixed.
Message life cycle
Understanding the life cycle of messages (emails) in AhaSend is important for being able to use the webhooks effectively. Once a message is received by AhaSend and queued for delivery, you a Message Reception event is fired. This is always the starting point of the a message's life on AhaSend.
Successful delivery on first attempt
All is well! The message was received by AhaSend and queued for delivery, and then successfully delivered to the recipient's email server.
![Message delivered on first attempt.](/sites/default/files/styles/large/public/2024-05/webhook-message-delivered_0.webp?itok=pBv9zDuH)
Hard Bounce on first attempt
This happens when the recipient mail server rejects the message with a 5xx SMTP error code, indicating that the message should not be retried delivery. This usually happens when the recipient does not exists (that is, the email address is incorrect). When a message is bounced, a message.bounced
event is fired, followed by a suppression.created
event to indicate that future message delivery attempts to this recipient will be suppressed (see below for more information on suppressions).
![Message bounced on first attempt.](/sites/default/files/styles/large/public/2024-05/webhook-message-bounced.webp?itok=avStVPPP)
Soft bounce followed by successful delivery
The recipient's mail server responded with a 4xx SMTP error, indicating that there's a temporary problem with delivering the email and message delivery should be deferred. This could be caused by the recipients inbox being full, or an internal issue with the mailbox provider. In this case, AhaSend will try delivering the message again for up 7 times within the next 24 hours. This means that you might receive multiple message.deferred
events for a single message.
![Message soft bounced and was finally delivered.](/sites/default/files/styles/large/public/2024-05/webhook-message-deferred-delivered.webp?itok=Omc_yNFQ)
Soft bounce followed by delivery failure
Similar to the previous scenario, this will happens when the message delivery fails after multiple delivery attemps. As mentioned above, there will be a maximum of 7 delivery attempts within 24 hours from the time the message was accepted by AhaSend for delivery, and if all 7 attempts fail, amessage.failed
event is fired, followed by a suppression.created
event to indicate that future message delivery attempts to this recipient will be suppressed (see below for more information on suppressions).
![Message soft bounced and was not delivered after multiple attempts.](/sites/default/files/styles/large/public/2024-05/webhook-message-deferred-failed.webp?itok=9RZrWQzO)
Message was suppressed
When delivery to a recipient fails due to a hard bounce or multiple soft bounces, a suppression is created to prevent future delivery attempts to the failing email address, and a suppression.created
event is triggered. Suppressions automatically expire after 30 days. Throughout this suppression period, AhaSend will accept messages for the suppressed recipient but will not attempt delivery, and instead, a message.suppressed
event is fired.
Mailbox providers expect senders to honor their notifications by refraining from sending additional emails once they have indicated that a message cannot be delivered to a recipient, due to reasons like non-existent addresses or full mailboxes. Ignoring these notifications can lead to the sender being blocked. To enhance deliverability and safeguard both your domain reputation and AhaSend's IP reputation, Suppressions are implemented to automatically prevent repeated delivery attempts when a mailbox provider deems a message undeliverable.
![Message suppressed. No delivery attempt was made.](/sites/default/files/styles/large/public/2024-05/webhook-message-suppressed.webp?itok=7_GwXDp-)
Create your webhook
- Go to the Webhooks page in your account
- Click on the +Add Webhook button
- In the URL field enter the endpoint URL where you want to receive the webhook events.
- In the events field, either choose "Send all events to this endpoint URL" or "I'll choose which events to send", if you chose the latter, select which events you want to receive.
Click on the +Create Webhook button. This will create the webhook and take you to the webhook details page.
Test your webhook
Once you've created your webhook, you can test it by clicking on the Send Test Event button in the webhook details page. Choose which events to receive for testing the webhook and click on the Send Test Events button. You will receive the webhook events at your endpoint wihtin a few seconds. You can use RequestBin to create a URL for quickly testing the webhook endpoints.
![Send test webhook events](/sites/default/files/inline-images/webhooks-send.png)
Securing your webhook
Webhooks are just HTTP requests from an unknown source, so verifying the authenticity of webhooks is a requirement for any secure webhook implementation.
AhaSend follows the Standard Webhooks specification for handling security by providing you with a Webhook Secret as a pre-shared key, and signing the webhook payload using HMAC with the webhook secret. You can use this secret to verify the webhook.
With each webhook request, you will receive the following three headers:
webhook-id
: You can use the value of this header as a unique identifier for the webhook event. This unique identifier is associated with a specific event trigger and remains consistent across multiple retries of a failed webhook. It is commonly used as an idempotency key, allowing the recipient to process a specific event just once, regardless of multiple transmissions due to malicious intent, errors, or network issues.webhook-signature
: The signature of the webhook payload. You can use the Webhook Secret for verifying this signature.webhook-timestamp
: Integer unix timestamp (seconds since epoch) of the time when the webhook event was sent.
To learn more about verifying the webhook signature please refer to the Verifying Webhook Authenticity section of the Standard Webhooks specification.
We recommend using the official Standard Webhooks libraries to verify signatures. You can perform the verification by initializing the webhook object with the Webhook Secret provided in the webhook details page, and passing the raw JSON payload and the HTTP headers to the Verify (or VerifyWebhook) method of the webhook object. If the verification fails, you will receive an error (or an exception is thrown).
import (
standardwebhooks "github.com/standard-webhooks/standard-webhooks/libraries/go"
)
wh, err := standardwebhooks.NewWebhookRaw(routeSecret)
err = wh.Verify(routingRequestPayload, routingRequestHeaders)
$wh = new \StandardWebhooks\Webhook::fromRaw($routeSecret);
$wh->verify($routingRequestPayload, $routingRequestHeaders);
The Standard Webhooks Repository includes reference implemenation libraries for working with webhooks compliant the Standard Webhoooks Spec. AhaSend Webhooks are compliant with these specs, and we recommend using these reference libraries for using AhaSend webhooks within your application code, or using them as the base or example for your custom implementation.
The reference implementation has libraries for the following languages:
Webhook data payload structure
AhaSend uses the full payload structure as recommended by the Standard Webhooks specification:
{
"type": "entity.event",
"timestamp": "ISO_8601_time",
"data": {
// data specific to the event `type`
}
}
type
can have any of the following values:
- message.reception
- message.delivered
- message.transient_error
- message.failed
- message.bounced
- message.opened
- message.suppressed
- suppression.created
- domain.dns_error
The payload for message
webhook events (e.g. any event with a type that starts with message.
) will have follow this format:
{
"type": "message.reception",
"timestamp": "2024-05-06T09:49:16.687031577Z",
"data": {
"account_id": "4cdd7bdd-294e-4762-892f-83d40abf5a87",
"event": "on_reception",
"from": "[email protected]",
"recipient": "[email protected]",
"subject": "Sample email for testing webhooks",
"message_id_header": "message-id-header",
"id": "ahasend-message-id"
}
}
The payload for suppression.created
webhook event has the following structure:
{
"type": "suppression.created",
"timestamp": "2024-05-06T12:57:06.451529527Z",
"data": {
"account_id": "4cdd7bdd-294e-4762-892f-83d40abf5a87",
"recipient": "[email protected]",
"created_at": "2024-05-06T12:57:06.451529617Z",
"expires_at": "2024-06-05T14:57:06.451529707+02:00",
"reason": "Too many hard bounces",
"sending_domain": "your-domain.com"
}
}
The payload for domain.dns_error
webhook event has the following structure:
{
"type": "domain.dns_error",
"timestamp": "2024-05-06T12:59:46.404433272Z",
"data": {
"domain": "example.com",
"account_id": "4cdd7bdd-294e-4762-892f-83d40abf5a87",
"spf_valid": false,
"dkim_valid": false,
"dmarc_valid": false,
"dns_last_checked_at": "2024-05-06T12:59:46.404433312Z"
}
}