# Validate Emails in ActiveCampaign

URL: https://email-validation.org/docs/guides/activecampaign/
Updated: 2026-11-06

> Documentation index: https://email-validation.org/llms.txt

Check the addresses of your ActiveCampaign contacts and tag each contact with the result, so your segments and
automations can leave out the addresses you should not mail. Two small pieces do the work, and you can use either or
both:

- **A sync command** checks every contact of a list (or of the whole account) that has no result yet, in one
  [bulk list](https://email-validation.org/docs/bulk/).
- **A webhook relay** checks each new contact within seconds.

Both run in your own accounts with your own keys: the command with Node, the relay as a Cloudflare Worker. Nothing is
in the ActiveCampaign apps marketplace yet, so there is nothing to install there.

## Before you start

- **An API key.** Create one on the [API keys page](https://email-validation.org/app/api-keys/). The relay needs the permission "Validate
  addresses"; the sync command also needs **"Bulk lists"**, which is off by default. A
  [daily credit cap](https://email-validation.org/docs/authentication/#limits) limits what a busy list can spend.
- **Your ActiveCampaign URL and key.** In ActiveCampaign: **Settings → Developer**. Copy the **URL** exactly as it is
  shown there, such as `https://<account>.api-us1.com`; the pieces send requests only to that host. The **Key**
  belongs to your user and can do what that user can do, so keep it secret. The user needs to view and edit contacts
  and manage tags.
- **Node 22.18 or later** for the sync command, and a **Cloudflare account** for the relay.
- **The files**, which are built from our tested code:
  - [sync.mjs](https://email-validation.org/downloads/activecampaign/sync.mjs): the sync command, one Node module;
  - [relay.js](https://email-validation.org/downloads/activecampaign/relay.js): the webhook relay, one Cloudflare Worker module;
  - [webhook.json](https://email-validation.org/downloads/activecampaign/webhook.json): the webhook's settings, for ActiveCampaign's API.

## The tags

| Tag | Meaning |
|---|---|
| `ev:valid`, `ev:invalid`, `ev:catch_all`, `ev:unknown`, `ev:disposable` | the [result status](https://email-validation.org/docs/statuses/); a contact has exactly one, and a new check replaces the old one |
| `ev:remove` | the [action](https://email-validation.org/docs/statuses/) is remove: do not mail this contact |

Spam traps and complainers are `ev:valid` with `ev:remove`, so build your segment on `ev:remove`: **Tag is ev:remove**
holds every contact you should not mail, and an automation can start when the tag is added. The pieces create their
six tags the first time they need them and never touch your own tags.

## Clean a list

A list's id is the number in the page's address while the list is open (**Contacts → Lists**), such as `3` in
`…listid=3`.

```bash
export EV_API_KEY=…  ACTIVECAMPAIGN_URL=https://<account>.api-us1.com  ACTIVECAMPAIGN_API_TOKEN=…
node sync.mjs --list 3 --dry-run    # counts what it would check
node sync.mjs --list 3              # checks and tags
node sync.mjs --list 3 --suppress   # also unsubscribes from list 3 the contacts whose action is remove
node sync.mjs --all                 # every contact of the account, on any list or none (tags only)
```

It lists the contacts, skips those that already have a status tag other than `ev:unknown` (add `--revalidate` to check
them again), checks the rest in one [bulk list](https://email-validation.org/docs/bulk/) and tags each contact. It prints contact ids and counts,
never addresses.

If a run stops for any reason, such as no credits or a closed laptop, run the same command again. It keeps the bulk
job's id in a small state file (ids only, no addresses) and resumes that job, so a list is never paid for twice.
Exit code `75` means the job was still running when the command stopped waiting: run it again later.

`--suppress` **unsubscribes** each contact whose action is remove from the list you passed. The contact keeps its data,
history and other lists, and you can subscribe it again. ActiveCampaign runs that list's unsubscribe automations and
notifications, so check them before the first run. Nothing is ever deleted.

Other options: `--status active,unconfirmed` for other list statuses, `--region eu` for
[EU data residency](https://email-validation.org/docs/eu-data-residency/), `--rate` for ActiveCampaign requests per second (default 4, at most 5, which
ActiveCampaign allows per account across all your apps), and `--wait-minutes` for how long to wait for a bulk list
(default 240). ActiveCampaign sends no webhook for contact imports, so run the sync after an import, or on a schedule.

## Check new contacts with the relay

1. Deploy the relay to your Cloudflare account and give it its secrets:

   ```bash
   npx wrangler deploy relay.js --name ev-activecampaign-relay --compatibility-date 2026-10-01 \
     --var ACTIVECAMPAIGN_URL:https://<account>.api-us1.com
   npx wrangler secret put EV_API_KEY --name ev-activecampaign-relay
   npx wrangler secret put ACTIVECAMPAIGN_API_TOKEN --name ev-activecampaign-relay
   npx wrangler secret put RELAY_SECRET --name ev-activecampaign-relay
   ```

   `RELAY_SECRET` needs at least 32 random letters and digits, for example from `openssl rand -hex 32`. For
   [EU data residency](https://email-validation.org/docs/eu-data-residency/), add `--var EV_REGION:eu` to the deploy command.
2. In ActiveCampaign, open **Settings → Developer → Webhooks → Add**:
   - **URL** the relay's address followed by `/` and your `RELAY_SECRET`, such as
     `https://ev-activecampaign-relay.<your-subdomain>.workers.dev/<RELAY_SECRET>`;
   - **List** the list whose new contacts to check, or all lists;
   - **Type** Contact added only; **Initialize from** all sources.

   To create the webhook with ActiveCampaign's API instead, send `webhook.json`, with your URL filled in, to
   `POST /api/3/webhooks`.
3. Add a test contact with the address `no_mailbox@sandbox.email-validation.org`, a [test address](https://email-validation.org/docs/test-addresses/) that
   costs nothing and receives no mail. Within a few seconds it has the tags `ev:invalid` and `ev:remove`. Then delete
   the test contact, and keep it out of campaigns until then.

The relay reads the contact by its id and checks the address stored on it, never an address in the delivery. The
secret in the URL is what proves a delivery comes from your webhook, so anyone who can read that URL can send
deliveries to the relay. Keep request logs that record URLs off for the Worker, and change the secret if it leaks.

## Answers and retries

ActiveCampaign never delivers a webhook again after a failure, and the same delivery can arrive twice. So the relay
tries a step once more itself when it meets a rate limit or an outage, and leaves anything it could not do to the
next sync run:

| The relay answers | When | What to do |
|---|---|---|
| `200` saved | The contact was checked and tagged | Nothing |
| `200` ignored or skipped | Not a new contact, the contact is gone, or it already has a status tag | Nothing |
| `200` not saved | The check ran, but ActiveCampaign did not take the tags | Run the sync command to catch up |
| `503` | A rate limit or an outage before the check | Run the sync command to catch up |
| `422` | Our API refused the key, there are no credits or the key reached its daily cap; or ActiveCampaign refused its key | Fix the cause, then run the sync command |
| `404` | The secret in the address is wrong | Check the webhook's URL |

Nothing is charged for a delivery that answers `503` or `422`. Before it checks, the relay reads the contact's tags
and skips one that already has a status tag, so a repeated delivery costs nothing. If you rely on the relay, run the
sync command on a schedule as well.

## Costs and limits

- Each checked address is one check. Results with the status `unknown` are free, and so are malformed addresses,
  duplicates in a list and the [test addresses](https://email-validation.org/docs/test-addresses/).
- ActiveCampaign allows 5 API requests a second per account. The sync command needs about one request per contact it
  tags (two for `ev:remove`) at its default rate of 4 a second, so a large account takes time.
- Our own [rate limits](https://email-validation.org/docs/rate-limits/) apply as to any other client.

## Privacy

The pieces send each address only to our API, for the check, and to your ActiveCampaign account's API for that
contact's tags. Their answers and output never contain an address or a key, and the relay writes no logs. The sync
command's state file holds job ids only.
