Plugin Events and Segment Rules
A plugin can record things that people do, such as orders. It can then add a rule to the segment editor, so that an admin can build a segment like “spent over 100 in the last 90 days”.
Do not confuse this with the Events Hook. That hook listens to what Broadcast publishes. This page is about events your plugin records.
How it works
- The top-level
eventskey in the manifest declares each event and its fields. - Your code records events with
ctx.broadcast.events.record. - The top-level
segmentRuleskey declares a rule on those events. - The rule appears in the segment editor under Plugins.
You never write SQL. Broadcast builds the query from your rule, and only from the fixed options on this page.
Full example: order events
This plugin has an API endpoint that records an order, and a rule named Total spent.
manifest.json:
{ "id": "local.order-events", "name": "Order events", "version": "1.0.0", "requires": { "broadcast": ">=3.0.0 <4.0.0", "pluginApi": "1" }, "hooks": { "apiEndpoints": [ { "name": "orders", "method": "POST", "permission": "subscribers_write" } ] }, "events": { "order.placed": { "value": "decimal", "properties": { "plan": "string" } } }, "segmentRules": { "total_spent": { "label": "Total spent", "source": "order.placed", "aggregate": "sum", "params": { "amount": "decimal", "days": "integer" }, "where": [["occurred_at", "within_days", "$days"]], "having": [">=", "$amount"] } } }
dist/main.js:
export default definePlugin({ apiEndpoints: { async orders(request, ctx) { const { order_id, email, total, plan } = request.body ?? {}; const result = await ctx.broadcast.events.record({ name: "order.placed", email, value: total, properties: { plan }, // The same order sent twice is stored once. idempotency_key: `order:${order_id}`, }); return result.ok ? { status: 201, body: { saved: true } } : { status: 422, body: result.error }; }, }, });
Declare an event
Event names are lower case words joined by dots, such as order.placed.
| Field | Options |
|---|---|
value |
decimal or integer. Leave it out if the event has no number |
properties |
Field names, each string, decimal, integer or boolean |
webhook |
true lets an admin send the event to a webhook endpoint. See Send an event to webhooks |
Record an event
await ctx.broadcast.events.record({ name: "order.placed", email: "ada@example.com", // or subscriber_id: 12 value: "19.50", properties: { plan: "pro" }, occurred_at: "2026-11-01T10:00:00Z", // optional idempotency_key: "order:1001", // optional, and strongly advised });
The call answers { ok: true }, or { ok: false, error: { code, message } } for a bad event. It does not throw for a bad event, so check ok.
With the same idempotency_key, an event is stored once. To record many events, use bulkRecord, with up to 1,000 items and a required idempotency_key for the whole call.
An event can arrive before the person subscribes. Broadcast keeps it by email, and links it when that email becomes an active subscriber of the channel.
Declare a rule
| Field | Meaning |
|---|---|
label |
The name in the segment editor. Required |
source |
An event that this plugin declares |
aggregate |
count, sum, min, max, last or exists. All but count and exists need the event to declare a value |
params |
Values the admin types in the editor: integer, decimal or string |
where |
Up to 5 filters, each [field, operator, operand] |
having |
[operator, number or $param]. Required, except for exists, which refuses it |
In where, a field is one of these:
| Field | Operators | Operand |
|---|---|---|
occurred_at |
within_days, before_days |
Whole days, or a $param |
value |
=, !=, >, >=, <, <= |
A number or a $param |
properties.<name> |
For numbers, the same six. For string and boolean, = and != |
A value or a $param |
A plugin can have up to 10 rules.
Send an event to webhooks
A plugin event can reach another system through Broadcast’s webhooks. Set webhook on the event:
{ "events": { "poll.answered": { "webhook": true, "properties": { "poll": "string", "answer": "string" } } } }
This adds a sensitive permission. The admin sees Its events in webhooks when installing the plugin.
Nothing is sent yet. An admin must turn the event on:
- Open Webhooks and edit an endpoint.
- Under Plugin events, tick
plugin.<plugin id>.<event>, such asplugin.broadcast.polls.poll.answered. - Save.
Select all does not tick plugin events. The API cannot turn one on. Each endpoint has its own choice.
A delivery uses the normal webhook format, with the same signature, retries and delivery log:
{ "id": 8123, "type": "plugin.broadcast.polls.poll.answered", "created_at": "2026-11-01T18:51:16Z", "data": { "event": "poll.answered", "plugin": "broadcast.polls", "subscriber_id": 12, "value": null, "properties": { "poll": "ship-it", "answer": "yes" }, "occurred_at": "2026-11-01T18:51:15Z" }, "delivery": { "attempt": 1, "delayed": false, "held_since": null, "reason": null } }
Broadcast builds data from the stored event. Your code cannot add a field.
- There is never an email address. A
stringproperty that holds an address is dropped. subscriber_idisnullwhen the event is not linked to a subscriber.valueis text for adecimal, and a number for aninteger.- A plugin that is off or removed sends nothing. A delivery that is still waiting is cancelled.
- A plugin can send 1,000 events a minute in each channel. Over the limit the event is saved, no webhook goes out, and the plugin log says so.
- A repeat with the same
idempotency_keysends nothing new. - When a subscriber is deleted, Broadcast deletes these webhook records too.
Common mistakes
You expect a webhook with no admin step. "webhook": true only makes the event available. An admin must tick it on an endpoint.
You record a field you did not declare. You get plan is not a declared property of this event. Add the field to the manifest. Only add to events: renaming a field breaks rules that people have saved.
You skip idempotency_key. A retry stores the event twice, and a sum counts it twice.
You send a bad value. "lots" for a decimal gives value must be a decimal.
You forget the rule depends on the plugin. If the plugin is off, its rule matches nobody. Admins see the field as not available.
Events are kept for two years. Events with no subscriber are kept for 90 days. Each plugin can keep 2,000,000 events in a channel. When a subscriber is deleted, their events are deleted.