package main
import (
"context"
"fmt"
"log"
"os"
"github.com/AhaSend/ahasend-go"
"github.com/AhaSend/ahasend-go/api"
"github.com/AhaSend/ahasend-go/models/requests"
"github.com/google/uuid"
)
func main() {
client := api.NewAPIClient(api.WithAPIKey(os.Getenv("AHASEND_API_KEY")))
accountID := uuid.MustParse(os.Getenv("AHASEND_ACCOUNT_ID"))
ctx := context.Background()
// The SDK sends a fresh Idempotency-Key with this POST and reuses it on
// its own retries, so a retried request is not applied twice.
response, _, err := client.WebhooksAPI.CreateWebhook(ctx, accountID, requests.CreateWebhookRequest{
Name: "Failures",
URL: "https://example.com/webhook",
Scope: "global",
Enabled: ahasend.Bool(true),
OnBounced: true,
OnTransientError: true,
OnFailed: true,
})
if err != nil {
log.Fatalf("Error creating webhook: %v", err)
}
// Use the secret to verify the signature of each delivery.
fmt.Printf("Created webhook %s, secret: %s\n", response.ID, response.Secret)
}
import { AhaSendClient } from "@ahasend/sdk";
const client = AhaSendClient.fromEnv();
// The SDK sends a fresh Idempotency-Key automatically and reuses it on its own retries.
const webhook = await client.webhooks.create({
name: "Delivery events",
url: "https://example.com/webhooks/ahasend",
scope: "global",
on_delivered: true,
});
console.log("Webhook created.", { id: webhook.id, name: webhook.name });
curl --request POST \
--url https://api.ahasend.com/v2/accounts/{account_id}/webhooks \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"name": "Delivery Webhook",
"url": "https://example.com/webhook",
"scope": "global",
"enabled": true,
"on_delivered": true
}
'import requests
url = "https://api.ahasend.com/v2/accounts/{account_id}/webhooks"
payload = {
"name": "Delivery Webhook",
"url": "https://example.com/webhook",
"scope": "global",
"enabled": True,
"on_delivered": True
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.ahasend.com/v2/accounts/{account_id}/webhooks",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'name' => 'Delivery Webhook',
'url' => 'https://example.com/webhook',
'scope' => 'global',
'enabled' => true,
'on_delivered' => true
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}HttpResponse<String> response = Unirest.post("https://api.ahasend.com/v2/accounts/{account_id}/webhooks")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"name\": \"Delivery Webhook\",\n \"url\": \"https://example.com/webhook\",\n \"scope\": \"global\",\n \"enabled\": true,\n \"on_delivered\": true\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.ahasend.com/v2/accounts/{account_id}/webhooks")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"name\": \"Delivery Webhook\",\n \"url\": \"https://example.com/webhook\",\n \"scope\": \"global\",\n \"enabled\": true,\n \"on_delivered\": true\n}"
response = http.request(request)
puts response.read_body{
"object": "webhook",
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"created_at": "2023-11-07T05:31:56Z",
"updated_at": "2023-11-07T05:31:56Z",
"name": "<string>",
"url": "<string>",
"enabled": true,
"on_reception": true,
"on_delivered": true,
"on_transient_error": true,
"on_failed": true,
"on_bounced": true,
"on_suppressed": true,
"on_opened": true,
"on_clicked": true,
"on_unsubscribed": true,
"on_complained": true,
"on_suppression_created": true,
"on_suppression_deleted": true,
"on_dns_error": true,
"on_contact_created": true,
"on_contact_updated": true,
"on_contact_deleted": true,
"on_contact_added_to_list": true,
"on_contact_removed_from_list": true,
"on_contact_subscribed": true,
"on_contact_unsubscribed": true,
"on_contact_complained": true,
"origins": [
"recipient"
],
"scope": "global",
"domains": [
"<string>"
],
"success_count": 1,
"error_count": 1,
"errors_since_last_success": 1,
"last_request_at": "2023-11-07T05:31:56Z",
"secret": "<string>"
}{
"message": "Error message"
}{
"message": "Error message"
}{
"message": "Error message"
}{
"message": "A request with this idempotency key is already in progress"
}{
"message": "idempotency key was already used with a different request payload"
}{
"message": "Error message"
}Create a Webhook via the API
Create an AhaSend webhook with the API: set the endpoint URL, choose event types and domain scope, and get the signing secret used for verification.
package main
import (
"context"
"fmt"
"log"
"os"
"github.com/AhaSend/ahasend-go"
"github.com/AhaSend/ahasend-go/api"
"github.com/AhaSend/ahasend-go/models/requests"
"github.com/google/uuid"
)
func main() {
client := api.NewAPIClient(api.WithAPIKey(os.Getenv("AHASEND_API_KEY")))
accountID := uuid.MustParse(os.Getenv("AHASEND_ACCOUNT_ID"))
ctx := context.Background()
// The SDK sends a fresh Idempotency-Key with this POST and reuses it on
// its own retries, so a retried request is not applied twice.
response, _, err := client.WebhooksAPI.CreateWebhook(ctx, accountID, requests.CreateWebhookRequest{
Name: "Failures",
URL: "https://example.com/webhook",
Scope: "global",
Enabled: ahasend.Bool(true),
OnBounced: true,
OnTransientError: true,
OnFailed: true,
})
if err != nil {
log.Fatalf("Error creating webhook: %v", err)
}
// Use the secret to verify the signature of each delivery.
fmt.Printf("Created webhook %s, secret: %s\n", response.ID, response.Secret)
}
import { AhaSendClient } from "@ahasend/sdk";
const client = AhaSendClient.fromEnv();
// The SDK sends a fresh Idempotency-Key automatically and reuses it on its own retries.
const webhook = await client.webhooks.create({
name: "Delivery events",
url: "https://example.com/webhooks/ahasend",
scope: "global",
on_delivered: true,
});
console.log("Webhook created.", { id: webhook.id, name: webhook.name });
curl --request POST \
--url https://api.ahasend.com/v2/accounts/{account_id}/webhooks \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"name": "Delivery Webhook",
"url": "https://example.com/webhook",
"scope": "global",
"enabled": true,
"on_delivered": true
}
'import requests
url = "https://api.ahasend.com/v2/accounts/{account_id}/webhooks"
payload = {
"name": "Delivery Webhook",
"url": "https://example.com/webhook",
"scope": "global",
"enabled": True,
"on_delivered": True
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.ahasend.com/v2/accounts/{account_id}/webhooks",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'name' => 'Delivery Webhook',
'url' => 'https://example.com/webhook',
'scope' => 'global',
'enabled' => true,
'on_delivered' => true
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}HttpResponse<String> response = Unirest.post("https://api.ahasend.com/v2/accounts/{account_id}/webhooks")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"name\": \"Delivery Webhook\",\n \"url\": \"https://example.com/webhook\",\n \"scope\": \"global\",\n \"enabled\": true,\n \"on_delivered\": true\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.ahasend.com/v2/accounts/{account_id}/webhooks")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"name\": \"Delivery Webhook\",\n \"url\": \"https://example.com/webhook\",\n \"scope\": \"global\",\n \"enabled\": true,\n \"on_delivered\": true\n}"
response = http.request(request)
puts response.read_body{
"object": "webhook",
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"created_at": "2023-11-07T05:31:56Z",
"updated_at": "2023-11-07T05:31:56Z",
"name": "<string>",
"url": "<string>",
"enabled": true,
"on_reception": true,
"on_delivered": true,
"on_transient_error": true,
"on_failed": true,
"on_bounced": true,
"on_suppressed": true,
"on_opened": true,
"on_clicked": true,
"on_unsubscribed": true,
"on_complained": true,
"on_suppression_created": true,
"on_suppression_deleted": true,
"on_dns_error": true,
"on_contact_created": true,
"on_contact_updated": true,
"on_contact_deleted": true,
"on_contact_added_to_list": true,
"on_contact_removed_from_list": true,
"on_contact_subscribed": true,
"on_contact_unsubscribed": true,
"on_contact_complained": true,
"origins": [
"recipient"
],
"scope": "global",
"domains": [
"<string>"
],
"success_count": 1,
"error_count": 1,
"errors_since_last_success": 1,
"last_request_at": "2023-11-07T05:31:56Z",
"secret": "<string>"
}{
"message": "Error message"
}{
"message": "Error message"
}{
"message": "Error message"
}{
"message": "A request with this idempotency key is already in progress"
}{
"message": "idempotency key was already used with a different request payload"
}{
"message": "Error message"
}scoped webhook
requires at least one domain and the key must have write permission for
every supplied domain. A global webhook requires
webhooks:write:all; supplied domains are accepted but ignored, and
the response contains domains: [].
Contact events (the on_contact_* fields) are available only to
accounts with the marketing features: turning one on for another
account returns HTTP 400. A webhook with any contact event on needs an
API key with contacts:read, and with lists:read as well for
on_contact_added_to_list, on_contact_removed_from_list,
on_contact_subscribed, on_contact_unsubscribed, and
on_contact_complained; without them the request returns HTTP 403.
The marketing check runs first.
Related Guides
- API authentication, Permission scopes, Webhook setup.
- Event reference, Webhook CLI, Local webhook testing.
- Signature verification, Retry policy, API errors.
- API rate limits.
Authorizations
API key for authentication. Non-empty Security Requirement values are AhaSend API-key roles. Roles listed within one requirement object are jointly required; separate requirement objects are alternatives.
Headers
Optional idempotency key for safe request retries. Must be a unique string for each logical request.
An identical request with a completed stored outcome returns the original status and body. An in-progress
execution returns 409, a changed method/path/body returns 422, and a released 5xx execution may run again.
Keys for non-secret responses expire after 24 hours. API-key create responses include a one-time secret_key,
so successful encrypted replay responses for those operations expire after 5 minutes.
255Path Parameters
Account ID
Body
Webhook name
255Webhook URL
Webhook scope
global, scoped Whether the webhook is enabled
Trigger on message reception
Trigger on message delivery
Trigger on transient errors
Trigger on permanent failures
Trigger on bounces
Trigger on suppressions
Trigger on opens
Trigger on clicks
Trigger on unsubscribes
Trigger on spam complaints
Trigger on suppression creation
Trigger on suppression deletion
Trigger on DNS errors
Trigger on contact creation
Trigger on contact updates
Trigger on contact deletion
Trigger when a contact is added to a list
Trigger when a contact is removed from a list
Trigger when a contact subscribes
Trigger when a contact unsubscribes
Trigger when a contact reports spam
The origins of the contact and suppression events that the webhook
gets. Omit it or send null for every origin. An empty array is
refused with HTTP 400: to stop contact or suppression events, turn
them off. An array that holds all four origins is saved as null,
so that an origin added later reaches the webhook too. Message
events and domain.dns_error have no origin, and the webhook always
gets them.
1Who made a change that a contact or suppression event reports:
recipient for the contact or recipient, through an unsubscribe link,
a one-click unsubscribe, or a spam complaint; api for you, through
the AhaSend API; dashboard for you, through the AhaSend dashboard;
system for AhaSend, for example after a bounce, an expired message or
an email validation, or when AhaSend support makes the change.
recipient, api, dashboard, system For scoped, a non-empty array is required. For global, this may
be omitted, null, empty, or non-empty; supplied values are accepted
but ignored.
Response
Webhook created successfully
Object type identifier
webhook Unique identifier for the webhook
When the webhook was created
When the webhook was last updated
Webhook name
Webhook URL
Whether the webhook is enabled
Trigger on message reception
Trigger on message delivery
Trigger on transient errors
Trigger on permanent failures
Trigger on bounces
Trigger on suppressions
Trigger on opens
Trigger on clicks
Trigger on unsubscribes
Trigger on spam complaints
Trigger on suppression creation
Trigger on suppression deletion
Trigger on DNS errors
Trigger on contact creation
Trigger on contact updates
Trigger on contact deletion
Trigger when a contact is added to a list
Trigger when a contact is removed from a list
Trigger when a contact subscribes
Trigger when a contact unsubscribes
Trigger when a contact reports spam
The origins of the contact and suppression events that the webhook
gets, or null for every origin. Message events and
domain.dns_error have no origin, and the webhook always gets them.
Who made a change that a contact or suppression event reports:
recipient for the contact or recipient, through an unsubscribe link,
a one-click unsubscribe, or a spam complaint; api for you, through
the AhaSend API; dashboard for you, through the AhaSend dashboard;
system for AhaSend, for example after a bounce, an expired message or
an email validation, or when AhaSend support makes the change.
recipient, api, dashboard, system Webhook scope
global, scoped Domains this webhook applies to. Always present; global webhooks return an empty array.
Number of successful calls
x >= 0Number of unsuccessful calls
x >= 0Number of consecutive failed calls. Reset to zero by the next successful call.
x >= 0When the webhook was last called
Webhook signing secret. Returned only when the webhook is created.

