Email automation events API: start and stop flows from your own system
ProfilePilot Team | Oct 4, 2026 | 6 min read
Send events such as an abandoned cart or a purchase from your shop, CRM or website, and add people to email flows, using a key that is shown only once.
What the events API does
This guide is for the team that connects your shop, CRM, booking system or website to ProfilePilot email flows. You do not need a ProfilePilot login to read it.
Your system tells ProfilePilot that something happened to a contact, for example cart_abandoned or purchase_completed. ProfilePilot then:
- starts any flow whose trigger is that event,
- wakes any flow step that is waiting for that event,
- removes the contact from any flow whose goal is that event.
You can also add a contact straight into a flow that is started by hand.
What it does not do. The API never creates contacts and never subscribes anyone. The email address must already be a contact in your workspace, and only subscribed contacts who have not unsubscribed, bounced or complained receive email. An event for a contact who is not subscribed is still recorded, but no email is sent.
Create an API key
Open Marketing → Email → Settings and scroll to API keys. Name the key after the system that will use it (for example "Online shop") and choose Create key.
Copy the full key straight away. ProfilePilot shows it once and stores only a scrambled copy. The key starts with ppem_.
- Call the API from your server only. Never put the key in a browser, mobile app, source control or a shared inbox.
- Create one key per system, so you can revoke one without stopping the others.
- Revoke a key from the same panel as soon as it is exposed or no longer used. A revoked key stops working immediately.
- A workspace can have up to 20 active keys.
Only the people who run email in the workspace can create or revoke keys. Approvers do not see the panel.
Base URL, headers and limits
- Base URL: https://profilepilotapp.com
- Authorization: Bearer followed by a space and your key.
- x-org-id (optional): if you send it, it must be the organisation UUID that owns the key. A mismatch is rejected with 403.
- Content-Type: application/json. Bodies may be up to 16 KB.
- Rate limit: 600 requests per minute per key. Above that you get 429 with a Retry-After header in seconds.
Every error is JSON in the form {"ok": false, "error": "a plain sentence"}.
Send an event: POST /api/v1/email/events
Send email (or contactId) and event. Event names use lower-case letters, numbers, dots, dashes and underscores, up to 64 characters.
- properties (optional): a flat object of text, number or true/false values, up to 50 keys. Emails in the flow can show them with merge tags such as {{event.cart_url}}.
- occurredAt (optional): when it happened, as an ISO-8601 time. It defaults to now.
- idempotencyKey (optional, recommended): your own unique id for this event, up to 200 characters. Sending the same key again does not record a second event.
Example: abandoned cart
curl -sS -X POST "https://profilepilotapp.com/api/v1/email/events" \
-H "Authorization: Bearer ppem_your_key" \
-H "Content-Type: application/json" \
-d '{
"email": "sam@example.com",
"event": "cart_abandoned",
"properties": { "cart_url": "https://shop.example/cart/abc", "value": 420 },
"idempotencyKey": "cart-abc-1"
}'
A new event returns 202:
{ "ok": true, "eventId": "8f1c2b40-6a1e-4d3a-9c2e-1b7a0e5d44a1", "duplicate": false }
Sending the same idempotencyKey again returns 200 with "duplicate": true and changes nothing. When the purchase happens, send purchase_completed for the same contact; the Abandoned Cart flow uses it as its goal and stops the reminders.
Add someone to a flow: POST /api/v1/email/enrollments
Use this for flows whose trigger is Manual / from a campaign. The flow must be turned on. Find the flow id in the address bar when you open the flow in ProfilePilot.
Example: start a sales follow-up
curl -sS -X POST "https://profilepilotapp.com/api/v1/email/enrollments" \
-H "Authorization: Bearer ppem_your_key" \
-H "Content-Type: application/json" \
-d '{
"workflowId": "YOUR_FLOW_ID",
"email": "sam@example.com",
"properties": { "meeting_url": "https://example.com/book" }
}'
The reply tells you whether the contact entered the flow:
{ "ok": true, "enrolled": true, "enrollmentId": "…" }
{ "ok": true, "enrolled": false, "reason": "not_subscribed" }
Other reasons are suppressed (unsubscribed, bounced or complained), already_enrolled and reentry_blocked (the flow does not let people go through again yet). Properties you send here are available as {{event.*}} merge tags in that flow.
Event names the starter templates use
You can use any event name. The starter templates expect these:
- Abandoned Form Nudge: starts on form_started (form_url is optional), goal form_completed.
- Abandoned Cart: starts on cart_abandoned (send cart_url; an email whose button needs a missing cart_url is skipped), goal purchase_completed.
- Post-Purchase Follow-up: starts on purchase_completed (order_id, order_url and review_url are optional).
- Free Trial Onboarding: starts on trial_started, goal trial_converted.
- New Customer Onboarding: starts on deal_won from your own CRM.
- Renewal Reminder: starts 30 days before the contact's renewal_date field; goal renewed.
- Birthday Greeting: starts every year on the contact's birthday field.
Date fields (birthday, renewal_date or your own) come from your contact data, for example a contacts import, as YYYY-MM-DD. Birthdays may also be MM-DD.
HTTP status codes
- 202 / 200: accepted (200 for a duplicate event or an enrolment that did not happen; read enrolled and reason).
- 400: the JSON could not be used, for example a missing event name or a nested property.
- 401: the key is missing, wrong or revoked.
- 403: x-org-id does not match the key, email is not enabled for the workspace, or the key lacks permission.
- 404: no contact with that email in this workspace, or the flow was not found.
- 409: the flow is not turned on, or it is not started by hand.
- 413: the body is larger than 16 KB.
- 429: too many requests; wait for Retry-After seconds.
- 500: ProfilePilot could not save it; retry with the same idempotencyKey.
Suggested first test
- Pick a subscribed test contact that you own.
- Create a flow from the Abandoned Cart template, review the emails, and turn it on.
- Send cart_abandoned with a cart_url and an idempotencyKey. Open the flow's Enrollments and confirm your contact is in progress.
- Send the same request again and confirm duplicate is true.
- Send purchase_completed for the same contact and confirm they leave the flow with "Reached the goal".
- Turn the flow off again if it was only a test.
Quick Tasks
- ✓Create an API key in Email → Settings → API keys and store it in a secrets manager
- ✓Set up a flow that starts on "Event from your system" and turn it on
- ✓POST one event for a subscribed test contact from your server
- ✓Send the same idempotencyKey again and confirm duplicate is true
- ✓Revoke any key that is no longer in use
Keep learning
See the latest guides, experiments, and rollouts.