API reference
Events
Record what your users do in your product
An event is something a contact did in your product: signed_up, trial_started, order_paid. Events can:
- start a flow whose trigger is this event name, such as an onboarding sequence on
signed_up(Flows); - filter a segment, such as "performed
order_paidin the last 30 days" (Segments).
Step-by-step example: Onboard users with events.
Record an event
POST /v1/events
Records an event for a contact, identified by email address, created if unknown. Allowed with a sending access key.
email
string required
The contact's address.
name
string required
1 to 100 letters, digits,
_, ., : or -, such as trial_started. Case-sensitive.properties
object
Details of the event, such as
{"plan": "pro", "amount": 49}. At most 50 keys and 10 KB.occurredAt
string
ISO 8601 date-time. Defaults to now.
contact
object
Contact fields to set:
firstName, lastName and attributes (merged, same rules as contact attributes).createContact
boolean
Create the contact when the address is unknown. Default
true; with false, an unknown address answers 404.curl https://api.kulamail.com/v1/events \
-H "Authorization: Bearer ml_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"email": "ada@example.com",
"name": "trial_started",
"properties": { "plan": "pro", "trial_days": 14 },
"contact": {
"firstName": "Ada",
"attributes": { "plan": "pro_trial" }
}
}'
await fetch('https://api.kulamail.com/v1/events', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.KULA_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
email: user.email,
name: 'trial_started',
properties: { plan: 'pro', trial_days: 14 },
contact: {
firstName: user.firstName,
attributes: { plan: 'pro_trial' }
}
})
})
201 Created
{
"id": "e1f2a3b4-c5d6-4e7f-8a9b-0c1d2e3f4a5b",
"contactId": "0d9e8f7a-6b5c-4d3e-2f1a-0b9c8d7e6f5a",
"name": "trial_started",
"properties": { "plan": "pro", "trial_days": 14 },
"occurredAt": "2026-10-17T09:30:00Z"
}
| Status | Code | When |
|---|---|---|
| 400 | validation_error, bad_request | Invalid name, too many properties, invalid address or attributes |
| 404 | not_found | Unknown address and createContact is false |
Good to know
- Events don't subscribe anyone. A contact created by an event belongs to no list, so it receives no campaign. Flows triggered by the event still email it.
- Personalize with attributes, not properties. Flow emails read
{{ contact.* }}, not event properties. Send values an email needs (the plan, the trial end date) incontact.attributes. - A flow only starts for active contacts. An event from an unsubscribed, bounced or complained contact is recorded but starts nothing.
- The same event twice starts the flow twice only if the flow lets contacts enter every time. By default, a contact goes through a flow once. Entry rules.
- Stripe or another tool? No native integration: forward their webhooks to this route.