Trigger Event
Post what happened and let Growth pick the workflow that answers it.
Enroll a contact by naming what happened instead of which workflow should answer. Your app posts an event — freebie, customer, subscription-cancelled — and the organization's routing table decides which workflow it enters.
Compared with Trigger Workflow, which pins the workflow id in your code:
- A new campaign sequence is a row in Email marketing > Workflows > Event routing, not an environment variable and a deploy on your side.
- Routing reads the contact's merged profile, not the request. A payment webhook carries no cookie, but the
utm_campaignwritten on the contact at signup three weeks earlier is still there — so the payment event can still be routed by the campaign that produced the lead. Your app cannot do that for itself. - Exactly one workflow is entered. Two sequences selling the same offer to the same contact is the failure this endpoint exists to prevent.
How A Route Is Chosen
Routes belong to an event. Each is either a rule (a contact field that must equal a value) or the event's default (no condition).
- Paused routes are skipped.
- Rules are tried first, highest
priorityfirst, ties going to the older rule. - The default catches everything no rule matched. A default never shadows a rule, whatever its priority.
A rule's field is read from the contact's custom fields, and from its tags as a fallback: utm_source matches both the custom field utm_source and the tag utm-source:instagram. Comparison ignores case and accents.
Request
POST /api/v1/email/events/triggerAuthentication (either header):
x-api-key: org_api_key
Authorization: Bearer org_api_keyBody:
| Field | Type | Required | Description |
|---|---|---|---|
event | string | Yes | Event name, normalized to lowercase [a-z0-9._-]. |
email | string | Yes | Contact email. Existing contacts are matched by organization and email. |
firstName | string | No | Contact first name. |
lastName | string | No | Contact last name. |
tags | string[] | No | Tags to add. Additive: nothing is removed. |
customFields | object | No | Merged into the contact and used for routing. Numbers and booleans are coerced to strings. |
Example:
curl -X POST https://your-growth-app.convex.site/api/v1/email/events/trigger \
-H "x-api-key: org_api_key" \
-H "Content-Type: application/json" \
-d '{
"event": "freebie",
"email": "[email protected]",
"firstName": "Lead",
"tags": ["freebie", "utm-campaign:ete-2026"],
"customFields": { "utm_campaign": "ete-2026", "ref": "gregoire" }
}'Response
200, with the enrolment decision alongside the upserted contact:
{
"contact": { "id": "m57...", "email": "[email protected]", "tags": ["freebie"] },
"created": true,
"enrolled": true,
"reason": "enrolled",
"event": "freebie",
"workflowId": "n97...",
"workflowName": "Freebie nurture — summer",
"matchedBy": "rule"
}A routing problem is not an error. The contact was merged, and that is a real effect the caller must not have to undo, so an unroutable event still returns 200 with enrolled: false and a reason to log:
reason | Meaning |
|---|---|
enrolled | The contact entered workflowId. |
no_route | Nothing is routed for this event yet. |
no_matching_route | Rules exist for the event, none matched, and there is no default. |
workflow_not_active | The routed workflow is a draft or paused. |
workflow_missing | The routed workflow was deleted. |
excluded_by_tags | The contact carries one of the workflow's exclude tags. |
Genuine errors:
| Status | Meaning |
|---|---|
400 | Missing event or email. |
401 | Missing or invalid API key. |
409 | The contact is unsubscribed, bounced or complained. |
422 | The contact's custom field cap was reached. |
Log enrolled and reason. no_route on an event you thought was configured is the one that costs you a nurture sequence, and nothing else will tell you.
Managing Routes From The API
Routes are editable in the UI and over the API.
# List (optionally filtered by event)
curl "https://your-growth-app.convex.site/api/v1/email/event-routes?event=freebie" \
-H "x-api-key: org_api_key"
# Create or update (pass routeId to update)
curl -X POST https://your-growth-app.convex.site/api/v1/email/event-routes \
-H "x-api-key: org_api_key" \
-H "Content-Type: application/json" \
-d '{
"event": "freebie",
"matchKey": "utm_campaign",
"matchValue": "ete-2026",
"workflowId": "n97...",
"priority": 10
}'
# Delete
curl -X DELETE https://your-growth-app.convex.site/api/v1/email/event-routes/<route-id> \
-H "x-api-key: org_api_key"Omit matchKey and matchValue to set the event's default. Both must be present together.
The routed workflow must use the API request trigger; saving a route to a workflow on any other trigger is refused at save time rather than failing silently at send time.
Partner-Owned Routes
A route can belong to an affiliate partner who built their own workflow from their portal. Those routes are pinned to the partner's own ref, sit above the organization's rules, and follow their workflow's status. The organization can pause them or change their priority but not repoint them — see Affiliate tracking setup.