ActiveCampaign
Paid add-on. Every submission creates or updates a contact. Subscribing to a list and applying tags, on the other hand, only happen with consent given by the person.
This document describes what the module guarantees and what it refuses. The user guide gives the short version, under “Sending answers to ActiveCampaign”.
A tag is not a tag
That is the difference governing the whole module, and it deserves to be said before the rest.
At HubSpot, a tag is a value on a CRM record: it sends nothing to anybody. The HubSpot module therefore lets tags travel with the contact, and only asks for consent for the list.
At ActiveCampaign, applying a tag commonly triggers an automation — that is the mechanism the platform puts forward for starting an email sequence. A tag there is therefore a marketing act on the same footing as a subscription.
Consent governs both here. That is not an inconsistency between the two modules: it is the same rule applied to two services that do not do the same thing with the same word.
The contact, meanwhile, goes out in every case: somebody wrote to the site, and that is where it gets noted.
Consent is a form field, and it is frozen
Not a box ticked in the admin: a box ticked by the operator would say “these submissions consent”, which is not a consent but an assertion made on people’s behalf.
The setting designates which field carries the box, and only the core’s Consent field is acceptable: it alone records, with the submission, the exact text accepted and the timestamp. An ordinary checkbox records only a “yes” — and a “yes” without the wording it approved demonstrates nothing.
The decision is written once, in the tracking row, when the submission arrives. Nothing re-reads it afterwards. Re-reading it at send time would make it depend on what an administrator had ticked in the meantime in the submission’s screen: in other words, a consent could be manufactured after the fact, by somebody else, with nothing to distinguish it from the real one.
Direct consequence, held by a test: ticking the box after the submission subscribes nobody, and a resend replays the submission with the consent it carried.
With no field designated, there is no state in which the module assumes agreement.
sync is a true upsert, and that is rare in this series
POST /contact/sync creates the contact, or updates the one carrying the same
address.
Google Sheets has no equivalent, nor does Pipedrive — for them, idempotence had to be built locally. Here the service provides it, and it covers the case no local guard sees: the request succeeds at ActiveCampaign, the response is lost on the way, the task is replayed.
The unique key and the row claim remain, for the common case — two tasks scheduled for the same submission. They serve less to avoid a duplicate contact than to avoid a second subscription and a second tagging: at ActiveCampaign, both restart the automations hooked to them.
Only the properties sent are written
sync does not erase what you do not give it — that is the opposite of HubSpot
and Pipedrive, where a property sent empty erases the record’s.
Empty values are removed all the same: sending an empty string would overwrite a field filled in through another channel.
File, signature, payment and password fields never go out, even if forcibly mapped. An automation platform copies those values into emails, and an upload address would become permanent access sent by post.
The ids are numbers, and that gets checked
Lists, tags and custom fields are designated by numbers. A value of another shape
would become 0 on casting — which ActiveCampaign would read as an id, not as an
absence. It is therefore set aside when the setting is saved.
The fields are offered on screen rather than typed: a wrong id does not fail loudly, ActiveCampaign ignores the value and records the rest.
One subtlety of PHP deserves noting here, because it produced a real difference: a
numeric array key becomes an integer again the moment the setting is written, so
that '12' and 12 designate the same entry. The conversion is therefore redone
explicitly at send time, so that what goes out always has the same shape.
The API’s address is typed in, and therefore checked
ActiveCampaign publishes no single API address: every account has its own —
mycompany.api-us1.com — which you read in your developer settings and copy into
the screen.
An address that is typed is an address to check. Without a check, a typo or an
unlucky paste would send the submissions and the API key to some arbitrary
host. The check therefore bears on the scheme — https only — and on two
suffixes: .api-us1.com and .activehosted.com.
The leading dot in the suffixes is deliberate. Without it, attacker-api-us1.com
would pass — that is the classic fault of this kind of list, and it does not show
on reading.
A refused address is not saved: doing so would produce a connection that looks configured and never calls. The message says what is allowed, because that is the only information that lets you correct it.
An API key does not expire
That is what makes it more dangerous than an access token: it opens the whole account — contacts, lists, automations, campaigns — for as long as nobody revokes it.
It is therefore encrypted at rest, with a key of this integration’s own, and never redisplayed — neither in full nor truncated. The plan proposed following Brevo, whose key lives in clear in the general settings; that is the last module doing so, and it was not a reason to add another.
Disconnecting does not revoke. Erasing the key here does not make it unusable: it is only revoked from the account that issued it. The screen says so rather than letting you believe that erasing is enough.
The key travels in an Api-Token header, never in the query string: an address
ends up in the server’s logs, in the intermediary’s, and in the history.
What is not attempted, and what is retried
Nothing is scheduled if the connection is missing, if the address field is not designated, or if the form’s condition is not met. A submission marked as spam does not go out, even if the task was already queued. An absent or malformed address is a definitive refusal written before any call.
Definitive refusals are not replayed — including the 403. Elsewhere it may
signal a momentary quota; here it signals a refused key, and a key does not become
valid again on its own. Replaying it three times would push back by thirty minutes
the moment the screen says so.
429 and 5xx are, at 1, 5 then 30 minutes.
What follows the contact never cancels the contact. A failed subscription or tagging leaves the send successful: the record is at the client’s, and the reason is written beside it. Replaying the whole send for a tag would also replay the subscription — and a second subscription restarts the automations.
An ActiveCampaign outage never blocks the submission. It is recorded, confirmed to the visitor and notified by email before this module is called on.
Resending updates, and reapplies
The resend button updates the same contact — sync finds it again by its address
— and creates no duplicate.
If consent was given, however, the list and the tags go out again, which restarts the automations hooked to them. The screen says so before the click.
Consent itself is not touched: a resend cannot invent one.
What the log does not contain
Neither the request, nor the response, nor the key. The first two carry the submission’s values: recording them would make the log a second copy, which would go off into backups and exports — where it would read as a technical trace when it describes a person. What remains is the HTTP code and the reason.
Schema
slf_activecampaign_contacts: one row per submission, with the contact’s id, the
frozen consent, the subscription and tagging actually made, the state, the reason
for the last problem and the number of attempts. It disappears with the
submission; the contact at ActiveCampaign stays.
The connection lives in an option: the address in clear — it is not a secret, and it is the first thing to check when nothing is going out — and the encrypted key.
What V1 does not do
No campaign, no automation created or triggered directly, no deal, no reading of contacts, no deletion, no reverse synchronisation. The flow goes from the site to the platform.
No unsubscribing either: removing somebody from a list from a form would require checking that it really is them asking, and that check does not belong to this module.
