Track an event
POST /api/v1/events
Records an event and attaches it to a contact, creating that contact if they don’t exist yet. Returns 201 Created.
Request
Human-readable event name, e.g.
Add to Cart. Any non-empty string is accepted — see How event names become types.Idempotency key for this occurrence — a cart ID, checkout ID, or order ID. See Idempotency; getting this wrong is the most common integration bug.
Who the event belongs to. Must contain either
contactUUID, or at least one of email / phone.Flat key-value object describing the event. Must not be empty, and must not contain nested objects.
Optional human-readable description, used when registering the event schema.
The contact object
Field names are snake_case. Unrecognized fields are rejected with
400, so emailAddress or firstName will fail — use email and first_name.
Example request
Response
201 Created
Idempotency
uniqueIdentifier is what stops a retried HTTP call from emailing your customer twice. Uniqueness is scoped to the combination of your organization, the event type, and the identifier.
So: use a stable ID per real-world occurrence (
order_55501), and a new ID when you want the flow to fire again.
Two things still happen on a resubmit: the contact is upserted (so profile fields update), and the event schema is re-registered. Only the automation trigger is suppressed.
How event names become types
You send a human-readableeventName. Retainful normalizes it and prefixes it with your integration’s key to produce the internal event type you’ll see in the dashboard:
Normalization uppercases the name and replaces every run of non-alphanumeric characters with a single underscore. The
REST-API. prefix comes from the integration the API key belongs to.
There is no fixed list of event names. Any non-empty string works — Quiz Completed and Subscription Renewed are as valid as the commerce names above.
Keep event names under about 50 characters. The internal type is stored in a 64-character column and includes the integration prefix; longer names fail on write.
eventData rules
- Must not be empty.
- No nested objects. A nested object is rejected with
400and a message naming the offending field. Flatten it:shipping_cityrather thanshipping: { city }. - Values may be strings, numbers, or booleans. Types are inferred and preserved for use in automation filters, so send
"amount": 19.99if you want to compare it numerically. - Useful keys:
product_id,product_name,quantity,price,currency,order_id,checkout_id,total,amount. - Every key becomes available in automation trigger filters and email personalization.
Register an event schema
POST /api/v1/events/register
Registers an event type and its shape without attaching it to a contact. Use it so the event’s fields appear in the automation builder’s filter and personalization pickers before any real event has fired — letting marketing build the flow while engineering ships the integration.
The event name to register.
An example payload. Retainful infers the field names and types from it.
Optional description of the event.
Designing good events
- Name by fact, not intent:
Subscription Renewed(what happened), notSend Renewal Email(what you want done). Marketing decides the response in the automation builder. - Keep names stable. Renaming orphans the automations and segments built on the old type.
- Include the fields you’ll filter on. If a flow should only fire for high-value renewals, send
amount. - Send one event per real occurrence, with a
uniqueIdentifierthat matches that occurrence’s own ID.
Event-triggered automations end to end
POST /api/v1/events/registerwith your event shape.- In the dashboard, create an automation triggered by your event, with filters like
plan is annual. POST /api/v1/eventsfrom production whenever it happens, with a freshuniqueIdentifiereach time.- Watch contacts flow through in the automation’s analytics.
Errors
See Errors for the response shape.