# Webhook Events Reference

> AcelleMail has three distinct webhook systems — customer-lifecycle webhooks (SaaS billing events), per-campaign webhooks (open/click/bounce on a specific campaign), and inbound webhooks from sending providers (SES SNS, Mailgun events). This reference documents each, with the real event names from the Acelle source — not the made-up ones some older guides list.

- Canonical URL: https://acellesend.com/kb/articles/webhook-events-reference
- Author: Tom Becker, Developer Editor
- Category: Developer Guide — REST API, webhooks, customization, extending source code, plugins
- Content type: reference · Difficulty: intermediate
- Tags: Acellemail
- Published: 2026-04-17 · Last updated: 2026-08-15
- Reading time: 9 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## What this is for

AcelleMail has **three distinct webhook systems**, and they're often confused. This reference documents each, with the real event names from the Acelle source (per `config/webhook_events.php` and the `webhooks` migration) — not the invented event names some older third-party guides list.

| System | Direction | Use case | Configured at |
|---|---|---|---|
| **Customer-lifecycle webhooks** | Outbound (Acelle → your app) | SaaS billing events — when a customer signs up, changes plan, cancels | Admin → Webhooks |
| **Per-campaign webhooks** | Outbound (Acelle → your app) | When a specific campaign sends / errors | Campaign → Edit → Webhooks tab |
| **Sending-provider notifications** | Inbound (sending provider → Acelle) | Bounce + complaint events from SES / Mailgun / SendGrid | `/api/v1/notification/bounce` + `/feedback` (auto-configured per sending server) |

Pick the right one for your use case before reading further.

## System 1 — Customer-lifecycle webhooks (the SaaS billing system)

This is the system **most people mean** when they ask about Acelle webhooks. Used by SaaS operators to keep their CRM, analytics, or billing system in sync with Acelle's customer + subscription state.

Configure at **Admin → Webhooks → New Webhook**. Each webhook subscribes to one event and posts to a URL of your choice.

### Available events

Per `config/webhook_events.php` — this is the authoritative list:

| Event | When it fires | Params posted |
|---|---|---|
| `new_subscription` | A customer starts a paid subscription on a plan | `customer_id`, `plan_id` |
| `cancel_subscription` | A customer cancels (subscription remains active until period end) | `customer_id`, `plan_id` |
| `terminate_subscription` | A subscription ends (after cancel grace period, or hard end) | `customer_id`, `plan_id` |
| `change_plan` | A customer moves between plans | `customer_id`, `old_plan_id`, `new_plan_id` |
| `new_customer` | A customer account is created | `customer_id` |
| `automation_webhook` | A "webhook" action fires inside an Acelle automation flow | `automation_id` |

> **`subscriber.created`, `campaign.opened`, `campaign.clicked` etc. are NOT events in this system.** Older docs sometimes list them; they don't exist. For subscriber + campaign events, use system 2 (per-campaign webhooks) or poll the API.

### Webhook delivery shape

```http
POST https://your-app.example.com/webhooks/acelle
Content-Type: application/x-www-form-urlencoded

customer_id=123&plan_id=45
```

Plus any auth header you configured (see below).

### Webhook configuration

The webhook form lets you set:

- **Name** — your label
- **Event** — one of the six above
- **Request method** — POST (default), GET, PUT, PATCH, DELETE
- **Request URL** — your endpoint
- **Authentication** — None, Bearer Token, Basic Auth, or Custom Header
- **Headers** — extra headers (e.g. `X-Source: acellemail`)
- **Body type** — `form-urlencoded` (default), `JSON`, `raw text`
- **Body params** — the event payload params (auto-populated) or your custom mapping
- **Retry on failure** — number of retries + delay between attempts
- **Status** — Active / Inactive

A webhook only fires when its `status = active`. Disable to pause without deleting.

### Triggering an automation_webhook from a flow

Inside an [automation flow](https://acellesend.com/kb/articles/advanced-automation-triggers-and-conditions), you can add a "Webhook" action. When a subscriber reaches that step, the configured `automation_webhook` fires with the `automation_id` (and optionally subscriber merge tags in the body).

This is the bridge from automation flows to external systems — fire a webhook to your CRM when a lead reaches "qualified", or to Slack when a high-value subscriber takes a key action.

## System 2 — Per-campaign webhooks

For events about a *specific campaign* (sent, opened, clicked, bounced, complained, unsubscribed), use the per-campaign webhook system.

Configure at **Campaign → Edit → Webhooks tab** (or via the campaign-creation wizard). Each campaign can have multiple webhooks; each subscribes to one event.

### Available campaign events

The per-campaign webhook table (`campaign_webhooks`) supports:

- `campaign.sent` — campaign send completed
- `campaign.open` — a subscriber opened the email
- `campaign.click` — a subscriber clicked a tracked link
- `campaign.bounce` — a delivery failure (hard / soft)
- `campaign.complaint` — a spam complaint
- `campaign.unsubscribe` — a recipient unsubscribed via the campaign

Payload includes the subscriber email, campaign uid, and event-specific fields (clicked URL, bounce reason, etc.).

### When to use this vs polling the API

| Pattern | Use when |
|---|---|
| Per-campaign webhook | You need real-time per-event reaction (push to Slack on bounce; sync to CRM on click) |
| Poll `/api/v1/campaigns/{uid}/...-log/download` | You need a batch import of all events for a campaign; or your app can't accept inbound HTTP |

For most external-system integrations, webhooks win — they're real-time and lower-cost than polling.

## System 3 — Sending-provider inbound notifications

The third system is for **inbound** webhooks — your sending provider (Amazon SES via SNS, Mailgun event hooks, SendGrid event webhook) posts bounce + complaint events to AcelleMail.

You usually don't configure these directly — Acelle auto-configures them when you set up the sending server (e.g. SES SNS topic + subscription is created during the SES sending-server wizard).

Routes:

- `POST /api/v1/notification/bounce` — receives bounce events
- `POST /api/v1/notification/feedback` — receives spam-complaint events

If you're building a custom sending provider integration, you'd POST events to these endpoints in the documented format. Otherwise, ignore — Acelle handles it transparently.

## Signature verification (where applicable)

For the **System 1 customer-lifecycle webhooks**, signature verification depends on what auth method you configured:

- **Bearer Token** — Acelle sends `Authorization: Bearer <your-token>`. Your endpoint checks the header matches the token you set. Simple, recommended.
- **Basic Auth** — Acelle sends `Authorization: Basic <base64(user:pass)>`. Your endpoint validates as usual.
- **Custom Header** — Acelle sends a custom header with a static value (e.g. `X-Webhook-Secret: my-shared-secret`). Verify equality with constant-time comparison.

Acelle's webhook system does NOT use HMAC signature on the body by default (unlike Stripe / GitHub). The auth header is the entire security mechanism. **Make sure your endpoint is HTTPS-only** so the auth header isn't exposed in transit.

If you need HMAC body signing, implement it via the **Custom Header** option — configure a header that contains the HMAC of the body computed in a Laravel listener you add via [extending source](https://acellesend.com/kb/articles/extending-acellemail-source-code).

## Retry behaviour

The webhook config lets you set:

- `setting_retry_times` — how many times to retry on non-2xx response
- `setting_retry_after_seconds` — wait time between retries

Recommended defaults: **3 retries with 60-second backoff**. Past 3 retries, the webhook is marked failed and won't fire again until manually re-enabled.

Your endpoint MUST:

- Return `2xx` within 30 seconds to count as success
- Be **idempotent** — Acelle may retry on transient failures; your endpoint should handle duplicate deliveries gracefully (key on `customer_id + event + timestamp`)
- Acknowledge quickly + process async — if your processing takes > 5s, accept the webhook, enqueue a job, return 200 immediately. Slow webhook handlers cause retry storms.

## Verifying webhooks work

```bash
# 1. Set up a temporary catch-all endpoint at https://webhook.site/
#    (or use ngrok to forward to localhost)

# 2. Configure a webhook in Admin → Webhooks → New
#    Event: new_customer
#    URL: <your webhook.site URL>

# 3. Trigger the event — create a new test customer in the admin

# 4. Check webhook.site — you should see the POST with customer_id in the body
```

If nothing arrives:

- Check **Admin → Webhooks → Logs** for the delivery attempt and the response code Acelle saw
- Verify your endpoint returned `2xx`
- Verify webhook status is "Active"
- Check the queue is processing (webhooks are dispatched via the same queue as the rest of Acelle — see [Setting Up Queue Workers](https://acellesend.com/kb/articles/setting-up-queue-workers-and-cron-jobs))

## Common issues

| What you see | Likely cause | Fix |
|---|---|---|
| No webhook fires when expected | Webhook status = Inactive, or event mismatch | Verify status is Active; verify the event matches what you actually triggered |
| Endpoint receives one webhook many times | Endpoint returned non-2xx; Acelle retried | Make endpoint return 2xx faster; or fix the underlying issue causing the error response |
| Webhook payload missing custom fields | Customer-lifecycle webhooks only carry `customer_id` / `plan_id`. Subscriber data isn't included | Use `customer_id` to fetch the customer via API for additional data |
| Webhook arrives without auth header | Auth method = None | Reconfigure with Bearer / Basic / Custom Header |
| Endpoint says "signature invalid" | You enabled HMAC verification but Acelle doesn't ship HMAC | Use header-based auth instead, OR implement HMAC via a custom listener (extending source) |
| Webhooks back up after a sending storm | Queue worker pool saturated | Increase worker numprocs (see [scaling guide](https://acellesend.com/kb/articles/scaling-for-100k-emails-per-day)) |
| Per-campaign webhook fires before "sent" event for transactional sends | Race condition between send + tracking event | Process events idempotently; don't assume strict ordering |
| Need a subscriber.created event | Doesn't exist in customer-lifecycle webhooks | Use per-campaign webhook for `campaign.sent`, OR poll the subscribers API for new records |

## FAQ

**Why no `subscriber.created` event?** Acelle's customer-lifecycle webhook system was designed for SaaS billing — it covers events the operator cares about (new customer, plan change, cancellation), not events the customer would care about (new subscriber on their list). For subscriber events, use the per-campaign webhook system (system 2) or poll the API.

**Can I add custom events?** Yes — fire a custom event from a Laravel listener (added via [extending source](https://acellesend.com/kb/articles/extending-acellemail-source-code)), then add it to `config/webhook_events.php`. It'll appear in the webhook config UI.

**Do webhooks count against API rate limits?** No — webhooks are outbound from Acelle, not API calls from your code.

**Can the webhook payload be JSON instead of form-urlencoded?** Yes — set "Body type" to JSON in the webhook config. The same param keys are posted, just JSON-encoded.

**How do I retry a manually-failed webhook?** Admin → Webhooks → Logs → click the failed delivery → "Retry". Or trigger the original event again.

**Where can I see what URL Acelle is calling?** Admin → Webhooks → click the webhook → see configured URL + recent delivery log.

**Can I forward webhook payloads to multiple endpoints?** Configure multiple webhooks for the same event, each with a different URL. Each fires independently.

## Related articles

- [Getting Started with the AcelleMail REST API](https://acellesend.com/kb/articles/getting-started-with-the-acellemail-rest-api) — for polling the API as an alternative
- [REST API Authentication and Endpoints](https://acellesend.com/kb/articles/rest-api-authentication-and-endpoints) — full endpoint reference
- [Extending AcelleMail Source Code](https://acellesend.com/kb/articles/extending-acellemail-source-code) — adding custom events / listeners
- [Setting Up AcelleMail as a SaaS Platform](https://acellesend.com/kb/articles/setting-up-acellemail-as-a-saas-platform) — webhooks pair with SaaS use case
- [Advanced Automation Triggers and Conditions](https://acellesend.com/kb/articles/advanced-automation-triggers-and-conditions) — fire webhooks from automation flows
- [Setting Up Queue Workers and Cron Jobs](https://acellesend.com/kb/articles/setting-up-queue-workers-and-cron-jobs) — webhook delivery uses the queue
- [Using API Triggers for Custom Automation Workflows](https://acellesend.com/kb/articles/using-api-triggers-for-custom-automation-workflows) — the inverse (your app calls Acelle)
- [Configuring Amazon SES with AcelleMail](https://acellesend.com/kb/articles/configuring-amazon-ses-with-acellemail) — SNS notification setup for inbound (system 3)

---

## Related articles

- [Getting Started with the AcelleMail REST API](https://acellesend.com/kb/articles/getting-started-with-the-acellemail-rest-api): AcelleMail ships a REST API that exposes nearly every operation the admin UI offers — create lists, add subscribers, send campaigns, fire automations, read reports. This guide walks the 4 things you need to make your first successful API call: where to find your token, how to send it, what to call first, and how to debug when it doesn't work.
- [REST API Authentication and Endpoints](https://acellesend.com/kb/articles/rest-api-authentication-and-endpoints): The complete reference for AcelleMail's REST API — all resources, all routes, the per-endpoint request/response shapes, and the scoping rules. Use this alongside the getting-started guide; this article is the lookup table.
- [AcelleMail Webhook Signature Verification and Replay Protection](https://acellesend.com/kb/articles/acellemail-webhook-signature-verification-and-replay-protection): When you receive a webhook from AcelleMail (or AcelleMail receives one from a third-party event source like Meta), the receiver has no built-in trust that the request came from where it claims. This guide walks the HMAC-SHA256 signature verification pattern AcelleMail uses internally for Ads webhook reception, the constant-time-compare hazard most operators get wrong on first attempt, the replay-protection cache pattern, and how to apply the same techniques to your own outbound integrations.
- [Extending AcelleMail Source Code](https://acellesend.com/kb/articles/extending-acellemail-source-code): When the REST API + webhooks aren't enough, AcelleMail's open Laravel codebase lets you extend it directly — add custom fields, integrate a custom sending provider, listen to internal events, override Blade views. This guide walks the upgrade-safe extension patterns, the project structure as it actually is (not what older docs say), and the discipline that keeps your customisations alive through Acelle upgrades.
- [BIMI with VMC — Cost, Process, and Whether It's Worth It](https://acellesend.com/kb/articles/bimi-with-vmc-cost-walkthrough): VMC (Verified Mark Certificate) costs $1,500/yr per domain and takes 4-8 weeks to obtain. This guide walks the cost math, the trademark requirements, the alternative paths, and a decision framework for whether VMC is right for your sending program.
