# API Triggers for Custom Automations — Wiring AcelleMail to Anything

> Your CRM, app, or backend can fire AcelleMail automations directly — no Zapier, no manual tagging. This guide walks the UI setup (custom-event triggers + API token generation) plus a complete payload + signing reference in the collapsible developer section.

- Canonical URL: https://acellesend.com/kb/articles/using-api-triggers-for-custom-automation-workflows
- Author: Sofia Lindqvist, Email Marketing Editor
- Category: Automation — Triggers, workflows, welcome series, drip campaigns, behavioral flows
- Content type: tutorial · Difficulty: advanced
- Tags: Acellemail
- Published: 2026-02-18 · Last updated: 2026-08-15
- Reading time: 9 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## When you need an API-triggered automation

The standard AcelleMail triggers (subscribed-to-list, email opened, tag added) cover most marketing-side events. The **Custom Event** trigger is for everything else:

- "Subscriber finished onboarding step 3 in our app" — fires from your backend
- "Customer renewed their subscription in Stripe" — fires from a Stripe webhook bridge
- "Order shipped from warehouse" — fires from your shipping integration
- "Support ticket closed" — fires from Zendesk / Intercom

The pattern is always: your-system → HTTPS POST to AcelleMail's events API → AcelleMail's automation triggered by that event name → email sent (or tag added, or webhook out, or whatever the flow does).

This article walks the AcelleMail-side setup. The developer-side code is in the collapsible.

## UI walkthrough — get your AcelleMail side ready

### Step 1: generate an API token

In AcelleMail's top-right user menu, click **API & Webhooks** → **API tokens** → **New token**.

Give it a descriptive name (`crm-bridge-2026` or `shopify-webhook-prod`). Copy the token immediately — AcelleMail shows it once.

If you scope tokens per integration, you can revoke just that one when the bridge moves or breaks, without affecting other integrations.

### Step 2: build the automation that listens

### Open the visual automation builder

In AcelleMail's left sidebar, click **Automation** → **Automations**. The index lists every automation in this account with its current state:

![Automations index](https://acellesend.com/plugins/acelle/landing/images/help/kb/welcome-automation/01-automations-index.png)

Click **New automation** in the top-right toolbar. The trigger picker opens:

![Trigger picker — pick what starts the automation](https://acellesend.com/plugins/acelle/landing/images/help/kb/welcome-automation/02-create-trigger-picker.png)


For the trigger picker, choose **Custom event**:

1. **Event name** — pick a stable identifier you'll send from your system. Examples: `onboarding_step_3`, `subscription_renewed`, `support_ticket_closed`. Lowercase + underscores, no spaces.
2. **Save** the automation

The automation is now listening. Add downstream steps (send email, add tag, send webhook):

![Listening automation — custom event trigger](https://acellesend.com/plugins/acelle/landing/images/help/kb/welcome-automation/03-flow-builder.png)

### Step 3: test from the AcelleMail UI

Inside the automation, click **Test** → **Simulate event** → fill in a test subscriber email + payload. AcelleMail runs the flow once with this test data:

![Automation flow — test mode](https://acellesend.com/plugins/acelle/landing/images/help/kb/automation-debug/02-automation-flow.png)

The automation log records the test run; verify your downstream email or webhook actions worked correctly before you wire up the real source system.

### Step 4: configure your external system to POST

Now fire the event from outside AcelleMail. The HTTP call:

```
POST https://acellemail.com/api/v1/events
Headers:
  Authorization: Bearer <your_api_token>
  Content-Type: application/json
Body:
  {
    "subscriber_email": "user@example.com",
    "event": "onboarding_step_3",
    "data": {
      "step_name": "added_first_team_member",
      "duration_seconds": 45
    }
  }
```

The `event` value must match the event name configured in step 2 above. The `subscriber_email` must already exist on the list the automation is scoped to (or use `upsert: true` to create-if-not-exists).

The `data` object is freeform — payload-shaped JSON your automation steps can read via `{{ event.data.step_name }}` etc.

### Step 5: monitor

The Automations index shows total runs per automation:

![Automations index — run counts](https://acellesend.com/plugins/acelle/landing/images/help/kb/automation-debug/01-automations-index.png)

Open the automation detail → run log → see every event received, with timestamp + subscriber + payload, plus the path each subscriber took through the flow:

![Run log + per-subscriber path](https://acellesend.com/plugins/acelle/landing/images/help/kb/automation-debug/02-automation-flow.png)

Failed runs (e.g. subscriber not found, payload invalid) show in red — click to see the error message.

## Common UI signals + fixes

| Symptom | Likely cause | UI fix |
|---|---|---|
| API returns 401 Unauthorized | Token typo'd or revoked | Regenerate token from API & Webhooks page; rotate in source system |
| API returns 404 Not Found | Wrong API path | Should be `https://your-acellemail-domain.com/api/v1/events` (not `/api/v2`) |
| API returns 200 but automation doesn't run | Event name in payload doesn't match automation's configured event name | Open automation → confirm the exact event name string |
| Automation runs but the email step skipped | Subscriber doesn't exist on the automation's scoped list | Either pre-create the subscriber, or use `upsert: true` in the API call |
| Same event fires multiple times | Bridge service retries on timeout, AcelleMail isn't idempotent on event-id | Include a unique `event_id` in payload; configure automation step to skip if event_id already processed |
| Payload data missing in email body | Merge tag uses wrong path | `{{ event.data.step_name }}` not `{{ step_name }}` — payload is nested under `event.data` |

## What event names work well

Naming events stably is one of the small things that pays back forever:

- **Use past tense** — `purchase_completed` not `purchase` (clear it already happened)
- **One concept per event** — `onboarding_step_3_completed` is fine; `onboarding_or_signup_or_invite` is not
- **Don't put data in the name** — `purchased_yoga_mat` is wrong; the product is in `data.product`, the event is `purchase_completed`
- **Match your internal product vocabulary** — if engineering calls it "session_started", call the event "session_started" too. Don't introduce a new vocabulary just for AcelleMail.

## Common automation patterns

| Use case | Event from your system | AcelleMail flow does |
|---|---|---|
| Onboarding nudge after 3 days idle | `onboarding_paused` | Sends "stuck on something?" email |
| Renewal confirmation | `subscription_renewed` | Tags as renewing-customer + thank-you email |
| Cart abandonment | `cart_abandoned` | Wait 1 hour → reminder email; wait 24 hours → discount email |
| Subscription cancel | `subscription_cancelled` | Add tag, suppress marketing sends, send win-back at 30/60/90 days |
| Customer success milestone | `nps_response_recorded` | If score >= 9, send referral CTA; if score <= 6, webhook to CRM ticket |
| Login activity | `user_logged_in` | Update last-active timestamp; trigger re-engagement if 30+ days gap |

<details>
<summary><strong>Developer: full API reference, signing, retries, batch endpoints, SDK examples</strong></summary>

The custom-event API and adjacent endpoints in detail.

**Authentication:**

```
Authorization: Bearer <token>
```

Tokens are issued per-account; rotate them when team members leave. Scope-per-integration is recommended — separate tokens for `crm-bridge`, `shopify-webhook`, `support-tool-sync` — so a leak from one source rotates without cascading.

**Fire a custom event:**

```bash
curl -X POST "https://acellemail.com/api/v1/events" \
  -H "Authorization: Bearer $ACELLE_TOKEN" \
  -H "Content-Type: application/json" \
  -H "X-Event-ID: $(uuidgen)" \
  -d '{
    "subscriber_email": "user@example.com",
    "event": "onboarding_step_3",
    "data": {
      "step_name": "added_first_team_member",
      "duration_seconds": 45,
      "completed_at": "2026-05-19T14:30:00Z"
    },
    "upsert": true
  }'
```

Response (200 OK):

```json
{
  "event_id": "evt_a1b2c3...",
  "subscriber_uid": "sub_x9y8z7...",
  "automation_runs_triggered": ["auto_onboarding_3_nudge"],
  "processed_at": "2026-05-19T14:30:01Z"
}
```

The `X-Event-ID` header is for idempotency — if your bridge retries on timeout, repeated calls with the same event-id are deduplicated server-side.

**Batch events** (for high-volume backfill):

```bash
curl -X POST "https://acellemail.com/api/v1/events/batch" \
  -H "Authorization: Bearer $ACELLE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "events": [
      {"subscriber_email": "a@example.com", "event": "purchase", "data": {...}},
      {"subscriber_email": "b@example.com", "event": "purchase", "data": {...}},
      {"subscriber_email": "c@example.com", "event": "purchase", "data": {...}}
    ]
  }'
```

Max 1000 events per batch. Useful for backfilling historical events when first integrating a CRM into AcelleMail.

**Webhook signing** (when AcelleMail webhooks out to your system):

If you configure AcelleMail to **Send webhook** as an automation step, AcelleMail signs every outbound request with an HMAC-SHA256 header:

```
X-AcelleMail-Signature: t=1684320000,v1=abc123def456...
```

Verify on your side:

```python
import hmac, hashlib, time

def verify_webhook(body, signature_header, secret):
    parts = dict(p.split('=') for p in signature_header.split(','))
    timestamp = parts['t']
    sig = parts['v1']
    payload = f"{timestamp}.{body}".encode()
    expected = hmac.new(secret.encode(), payload, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, sig)
```

Reject any request older than 5 minutes (replay protection) and verify the signature matches — this prevents anyone-with-the-URL from forging requests.

**Retries:**

AcelleMail's outbound webhooks retry with exponential backoff on 5xx responses: 1m, 5m, 30m, 2h, 12h, 24h. After 6 failed attempts the webhook is marked failed-permanent and the automation step's run shows red.

For inbound events FROM your system, you control retries — if AcelleMail returns 5xx, retry with exponential backoff. Don't retry on 4xx (bad payload — retrying won't fix it).

**SDK examples:**

```javascript
// Node.js
const fetch = require('node-fetch');

async function fireEvent(email, event, data) {
  const res = await fetch('https://acellemail.com/api/v1/events', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.ACELLE_TOKEN}`,
      'Content-Type': 'application/json',
      'X-Event-ID': crypto.randomUUID(),
    },
    body: JSON.stringify({
      subscriber_email: email,
      event,
      data,
      upsert: true,
    }),
  });
  if (!res.ok) throw new Error(`AcelleMail event failed: ${res.status}`);
  return res.json();
}

// Usage:
await fireEvent('user@example.com', 'onboarding_step_3', {
  step_name: 'added_first_team_member',
});
```

```python
# Python
import requests, uuid, os

def fire_event(email, event, data):
    res = requests.post(
        'https://acellemail.com/api/v1/events',
        headers={
            'Authorization': f"Bearer {os.environ['ACELLE_TOKEN']}",
            'Content-Type': 'application/json',
            'X-Event-ID': str(uuid.uuid4()),
        },
        json={
            'subscriber_email': email,
            'event': event,
            'data': data,
            'upsert': True,
        },
        timeout=10,
    )
    res.raise_for_status()
    return res.json()

# Usage:
fire_event('user@example.com', 'onboarding_step_3', {
    'step_name': 'added_first_team_member'
})
```

```php
// PHP / Laravel
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Str;

function fireAcelleEvent(string $email, string $event, array $data): array
{
    $response = Http::withToken(config('services.acelle.token'))
        ->withHeaders(['X-Event-ID' => (string) Str::uuid()])
        ->timeout(10)
        ->post('https://acellemail.com/api/v1/events', [
            'subscriber_email' => $email,
            'event' => $event,
            'data' => $data,
            'upsert' => true,
        ]);

    $response->throw();
    return $response->json();
}

// Usage:
fireAcelleEvent('user@example.com', 'onboarding_step_3', [
    'step_name' => 'added_first_team_member',
]);
```

**Local development testing** — use [webhook.site](https://webhook.site) or [ngrok](https://ngrok.com) to expose your local dev server to AcelleMail's webhook deliveries while developing:

```bash
ngrok http 3000
# AcelleMail webhook URL: https://<ngrok-id>.ngrok.io/your-endpoint
```

Update the automation's webhook destination during dev; flip back to your production URL when shipping.

**Rate limits:**

The events API accepts 1000 requests/minute per token. Burst higher than that returns 429 with `Retry-After` header — back off and retry. For high-volume systems, use the batch endpoint instead.

**Schema versioning:**

If your event payload schema changes (e.g. you rename `data.product` to `data.sku`), bump the event name explicitly: `purchase_completed` → `purchase_completed_v2`. AcelleMail automations target one event name; switching is opt-in per-automation, so old data and new data don't crash each other.

**Audit trail:**

Every API call to `/api/v1/events` is logged in AcelleMail's audit log (admin → Audit log). Useful for forensics when a bug fires unexpected events — search by token, by event name, by subscriber email.

</details>

## Related articles

- [Advanced automation triggers and conditions](https://acellesend.com/kb/articles/advanced-automation-triggers-and-conditions)
- [Post-purchase follow-up automation](https://acellesend.com/kb/articles/post-purchase-follow-up-automation)
- [Lead scoring with email automation](https://acellesend.com/kb/articles/lead-scoring-with-email-automation)
- [Building a welcome email series in AcelleMail](https://acellesend.com/kb/articles/building-a-welcome-email-series-in-acellemail)

---

## Related articles

- [Build a Welcome Email Series in AcelleMail (Visual Flow Builder)](https://acellesend.com/kb/articles/building-a-welcome-email-series-in-acellemail): AcelleMail's automation builder ships with 12 triggers. "Welcome new subscribers" + 5 email nodes + a WAIT + a YES/NO CONDITION gives you a working welcome series in 30 minutes. Walkthrough with the actual flow canvas.
- [Automation Triggers + Conditions: Full Reference](https://acellesend.com/kb/articles/advanced-automation-triggers-and-conditions): Complete reference for AcelleMail's 12 built-in triggers, the CONDITION node operators, and the OPERATION actions. Plus the multi-branch patterns power users actually deploy.
- [Post-Purchase Follow-Up Automation in AcelleMail — Thank, Review, Cross-Sell](https://acellesend.com/kb/articles/post-purchase-follow-up-automation): A confirmation email is just the start. Layer in a delivery-day check-in, a 7-day review request, a 30-day cross-sell, a 60-day "we miss you". This guide builds the full post-purchase flow in AcelleMail's automation builder using tags or custom events fired from your ecommerce backend.
- [Lead Scoring with AcelleMail Automation — Score, Tag, and Route](https://acellesend.com/kb/articles/lead-scoring-with-email-automation): Add points when a subscriber opens a campaign, clicks a key link, or fills a form. Route high-score subscribers to a sales-handoff tag automatically. This guide walks the AcelleMail automation builder + custom-field scoring, with API-driven scoring patterns for ecommerce + CRM integrations in the collapsible section.
- [Birthday & Anniversary Automations in AcelleMail — Date-Triggered Sends](https://acellesend.com/kb/articles/birthday-and-anniversary-email-automations): Send a happy-birthday discount on the day, a year-since-signup gift on the anniversary, a renewal reminder 30 days before expiry. AcelleMail's date-relative automation trigger handles all of these — this guide walks the setup with screenshots.
