The Custom Events API allows you to capture timestamped data for specific events, like “Offer Made”, “Payment Received”, “Tag Added”, etc.
Overview
Previously in Streamlined Analytics, if you wanted to see when something happened, you were limited to the basic fields, like contact/opportunity/appointment created at, last updated, etc.
There was no way to track specific events, like when a tag was added or when a contact hit a certain stage in the pipeline.
Now though, we have a dedicated API endpoint and filter builder for these events!
Creating Events
Using the custom events API is easy and free.
Create an API Key
First, head to Settings > Integrations and click “New Token”:
The modal asks three questions: what the token may do, which sub-accounts it may act on, and what to call it.
For events, choose the Send custom events preset. It grants exactly the three event capabilities — create, update and delete — and nothing else, so the token cannot read your CRM data or touch your dashboards even if it leaks.
You can also pin the token to specific sub-accounts. A pinned token refuses any event naming a location_id outside its list.
On the last step you’ll see the token itself:
Endpoint Basics
We’ll dive into how your events should be structured below. However, the basics are here:
{
"endpoint_url": "https://api.streamlined.so/gateway/v2/custom-event",
"method": "POST",
"headers": "X-API-Key"
}Every response from this endpoint carries an ok field. Branch on that rather than on the status code — it is the field the gateway actually writes, and it survives anything that reads the body without the response object:
{ "ok": true, "result": { ... } }
{ "ok": false, "error": "..." }If your sender cannot set headers, put the token in the URL instead: https://api.streamlined.so/gateway/v2/custom-event?token=<access-token>.
Prefer the header wherever you can — a URL is far likelier to end up in a log or a referrer. If you do use the URL form, treat the whole URL as a secret and pin the token to the Send custom events preset so a leak can do nothing else.
Two Success Statuses
A successful create answers one of two statuses, and they mean different things:
Status | What happened |
201 | The event was written. The response carries the stored row, including the id you need to update or delete it later. |
202 | The event named a contact_id we have never seen. The event is held out of reporting and queued for enrichment instead, so the response carries no row. |
Treat 202 as accepted-but-not-yet-stored. Polling for the event immediately after one will not find it.
Use a Workflow to Send Events
The easiest way to send events to Streamlined is using the Webhoook workflow action in CRM.
This Webhook action (not the Custom Webhook) is a free workflow action, and will not incur any additional cost.
Event Payload
Here are the possible event values you can send:
The only required fields in this payload are event_name and location_id.
This also means that every event must be associated to a single sub-account.
distinct_id is optional, and it decides whether an event can be corrected later.
Leave it out and we generate one for you. The event is a fact — nothing can address it afterwards by a key you chose.
Send one and it becomes the upsert key, unique per distinct_id + sub-account. Posting the same distinct_id twice updates the event rather than duplicating it, which is what makes a retrying sender safe.
An upsert never moves an existing event’s timestamp. When something happened is the fact the event exists to record, so a repeat write leaves it where it was.
timestamp must be an ISO 8601 string. UNIX seconds and milliseconds are rejected. Leave it out and the event is stamped on arrival.
Payload Examples
Here is an example, sending a minimal payload using the Webhook workflow action in the CRM:
Sending Custom Properties
If you wish to send properties along with your event, you may need to use the Custom Webhook workflow action instead (which is billed as a premium action).
Note that these custom properties can’t yet be filtered on or displayed in the dashboard.
Here’s an example of how you might send the event:
{{location.id}} does not exist. It’s safe to ignore this warning.Note how we created the distinct_id property here. We used the month and year, like “09/25” - meaning that this will allow us to repeat the event every month, but we can’t send the same event every month.
Correcting Events
Events are no longer write-once. Two more methods on the same URL let you fix an event you already sent, using the id that a 201 create handed back.
Update an Event
PATCH https://api.streamlined.so/gateway/v2/custom-event?id=<event-id>
X-API-Key: <access-token>
Content-Type: application/jsonName at least one of name, contact_id or properties. A body with none of them is refused rather than filed as a write that changed nothing:
{
"name": "Payment Received",
"contact_id": "abc123abc123abc123ab",
"properties": { "set": { "plan": "pro" } }
}The rename field is name, not event_name. event_name is what you send when creating an event; name is what you send when correcting one.
Properties move as deltas, so a sender that only knows about one key does not have to send the whole bag back and silently discard whatever changed in between. Exactly one of these per call — a body carrying two is rejected:
Edit | What it does |
{ "set": { "plan": "pro" } } | Merge these keys in, leaving the rest alone. |
{ "unset": ["trial_ends"] } | Drop these keys. |
{ "replace": {...} } | State the whole bag. "replace": null clears every property. |
Two fields cannot be edited. timestamp stays put — when something happened is the fact the event exists to record, so allowing an edit would make an event’s time depend on which door the correction came through. location_id is half the event’s identity, so moving one is a delete and a create rather than an edit.
contact_id is editable and accepts null, which detaches the contact — the correction for an event attributed to the wrong person. Unlike create, attaching a contact we have never seen is refused here rather than queued for enrichment.
Delete an Event
DELETE https://api.streamlined.so/gateway/v2/custom-event?id=<event-id>
X-API-Key: <access-token>Permanent, and takes effect immediately in reporting.
Displaying Events
In order to display custom events, you can simply add them to your custom metric calculations as shown below:
contact_id, but allow you to filter based on contact fields like date created or tags.Other Details
Deleting Events
Events are deleted one at a time with DELETE /gateway/v2/custom-event?id=<event-id>, as described above.
There is no bulk delete. If you’d like a large set of events removed, you should Get in Touch.
Also be aware that if your events are tied to a specific contact_id, deleting that contact will cascade the deletion to any custom events for that contact as well.
Reading Events Back
There is no GET on this endpoint, and that is deliberate — reading events is what the query API is for. Query the events table through Relational MCP & API instead, where you can join, filter and aggregate them alongside the rest of your CRM data.
Other Relations
In the future, we plan to add support for relating custom events to other objects, such as opportunities, appointments, tasks, notes, etc.
Please request these if you would like them to be supported.
Distinct Events
If you send two events with the same distinct ID, the event will be updated, but the timestamp will remain the same.
Rate Limits
The API allows 500 requests per 10 seconds per token, which is far above what any normal event sender needs. Exceed it and you get a 429 naming the limit, along with a Retry-After header telling you how long to wait.
The limit is counted per token, so one runaway integration cannot throttle your other credentials or your dashboards.
Frequent Questions
Do you offer custom development work?
Should I install your app at the agency level or location level?
What’s your support like?
How do I install your app?
Do you offer a free trial?
Can I request a feature or product?
Are there any usage limits on your apps?
Can I use your app on multiple accounts?
How do I uninstall the app?
Need to Get in Touch?
If you have any questions, concerns, or ideas, I’d love to hear them!
Visit the page below to book a call or get in touch right away.
