Add a contact to a campaign via the API
Enroll a person in a campaign's email sequence from your server when there is no tracked click to attribute, such as an organic signup or a CRM sync.
A person enters a campaign’s email sequence in one of three ways: they become a lead through a tracked link, you add them by hand on the Recipients tab, or your server adds them with this endpoint. Use it when signups reach you without a click id, for example organic traffic, a newsletter form, or a sync from your CRM.
Endpoint
Section titled “Endpoint”The slug is the last part of the campaign’s address in the app, app.linkifunnels.com/campaigns/<slug>. Authenticate with an API token from Settings, then Connect your site, then Manage tokens, passed as a bearer token. Tokens are scoped to one workspace. See Authenticate API requests.
curl -X POST \ https://app.linkifunnels.com/api/v1/campaigns/welcome-series/leads \ -H "Authorization: Bearer $LINKIFUNNELS_API_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "email": "dana@northwind.io", "name": "Dana Whitaker", "external_id": "cus_8201" }'const url = "https://app.linkifunnels.com/api/v1/campaigns/welcome-series/leads";const response = await fetch(url, { method: "POST", headers: { Authorization: `Bearer ${process.env.LINKIFUNNELS_API_TOKEN}`, "Content-Type": "application/json", }, body: JSON.stringify({ email: user.email, name: user.name, external_id: user.id, }),});const { outcome } = await response.json();When to use it instead of the conversions API
Section titled “When to use it instead of the conversions API”If the visitor arrived through a tracked link, send the click id to the conversions API instead. That credits the post that produced the lead and enrolls the person in the campaign automatically. This endpoint has no click to attribute, so it records the lead against the campaign directly. The person counts in the campaign’s leads, appears on the Recipients tab and in Contacts with “API” as how they came in, and does not appear in any post’s numbers.
Request body
Section titled “Request body”Send JSON. Only email is required.
| Parameter | Type | Required | Description |
|---|---|---|---|
email |
string | Yes | The person’s email address. Matched case-insensitively against the contacts in your workspace |
name |
string | No | Stored on the contact. An existing name is overwritten |
external_id |
string | No | A stable id from your system, the same one you send to the conversions API, so a later sale ties to this contact |
{ "email": "dana@northwind.io" }Response
Section titled “Response”The response always carries an outcome so you can tell what happened without inspecting the rest.
| Status | outcome |
Meaning |
|---|---|---|
201 |
created |
A new contact was created, recorded as a lead of the campaign, and enrolled |
201 |
reused |
An existing contact was recorded as a lead and enrolled, or a stopped enrollment was resumed from its next unsent email without a new lead |
200 |
already_enrolled |
The contact is already in the sequence. The enrollment is untouched, though a name or external_id you sent is still applied to the contact |
200 |
suppressed |
The address has unsubscribed or bounced. Nothing was enrolled and enrollment is null |
The call is idempotent. Sending the same email twice returns already_enrolled the second time, so retries after a timeout are safe. The first email goes out after its configured delay, counted from the moment of enrollment, once the sequence is live. A campaign without an active email accepts contacts too: they wait with next_send_at set to null, and the first send is scheduled when you activate an email.
{ "outcome": "created", "contact": { "id": 1204, "email": "dana@northwind.io", "name": "Dana Whitaker", "external_id": "cus_8201" }, "enrollment": { "id": 5531, "state": "active", "enrolled_at": "2026-09-22T09:41:12Z", "next_send_at": "2026-09-22T09:41:12Z" }}Unsubscribes and limits
Section titled “Unsubscribes and limits”Anyone who unsubscribed from your emails or whose address bounced is on your workspace’s suppression list and is never enrolled, whichever way they arrive. See Handle unsubscribes and bounces. Enrollments created this way count toward your sending limits like any other, and sending pauses automatically when recipients complain.
Errors
Section titled “Errors”| Status | Body | Meaning |
|---|---|---|
401 |
unauthorized |
The bearer token is missing or revoked |
404 |
unknown campaign |
No campaign with that slug exists in the token’s workspace |
422 |
email is required |
The request had no email |
429 |
rate_limited |
More than 120 enrollments per minute on this token |
{ "error": "unknown campaign" }