Rule-Triggered Webhooks
Use the Rule Builder to receive a webhook only when a chosen condition matches — for example a specific harm label. Stream sends moderation_rule.triggered for that rule. Other messages do not produce this event.
This is the scoped alternative to subscribing to every review-queue item. Pair it with webhook_only when you want a notification, or with a user/content action when you also want Stream to enforce something.
How it works
- Create a rule whose conditions describe the harms (or other signals) you care about.
- Set the rule action to
webhook_only, or to a user/content action such asflag_user. - Subscribe an event hook to
moderation_rule.triggeredwith productmoderation.
When a check evaluates that rule and the conditions pass, Stream sends one moderation_rule.triggered event. Rule actions run in addition to the action already configured on the policy for that label.
Content rule: notify on a harm label
A content rule with text_content evaluates the current message, activity, or comment. It fires as soon as the listed labels are present.
{
"name": "High-profile harms",
"description": "Notify when selected harm labels are detected",
"rule_type": "content",
"enabled": true,
"config_keys": ["chat:messaging", "chat:livestream"],
"conditions": [
{
"type": "text_content",
"text_content_params": {
"harm_labels": ["UNDERAGE_USER", "SCAM"]
}
}
],
"action": {
"type": "webhook_only"
}
}| Field | Description |
|---|---|
rule_type |
"content" evaluates the current piece of content. |
config_keys |
Policies this rule applies to. An empty list applies the rule app-wide. |
text_content_params.harm_labels |
Labels that satisfy the condition. Any listed label is enough. |
text_content_params.severity |
Optional minimum severity (LOW, MEDIUM, HIGH, CRITICAL). |
action.type |
"webhook_only" sends the event and does not apply a further enforcement action. |
Create or update the rule with POST /api/v2/moderation/moderation_rule. Enable the Rule Builder on the same config:
{
"key": "chat:messaging",
"rule_builder_config": {
"enabled": true
}
}client.Moderation().UpsertConfig(ctx, &getstream.UpsertConfigRequest{
Key: "chat:messaging",
RuleBuilderConfig: &getstream.RuleBuilderConfig{Enabled: getstream.PtrTo(true)},
})
client.Moderation().UpsertModerationRule(ctx, &getstream.UpsertModerationRuleRequest{
Name: "High-profile harms",
Description: getstream.PtrTo("Notify when selected harm labels are detected"),
ConfigKeys: getstream.PtrTo([]string{"chat:messaging", "chat:livestream"}),
Enabled: getstream.PtrTo(true),
RuleType: "content",
Conditions: getstream.PtrTo([]getstream.RuleBuilderCondition{
{
Type: getstream.PtrTo("text_content"),
TextContentParams: &getstream.TextContentParameters{
HarmLabels: []string{"UNDERAGE_USER", "SCAM"},
},
},
}),
Action: &getstream.RuleBuilderAction{Type: "webhook_only"},
})Other conditions on a content rule work the same way: blocklist_match, contains_url, image harm_labels, and so on. See Rules.
User rule: act on the account
A user rule with text_rule counts matching labels for one user over a time window. Use this when the signal should follow the account rather than a single piece of content.
{
"name": "Repeat high-severity insult",
"rule_type": "user",
"enabled": true,
"config_keys": ["chat:messaging"],
"conditions": [
{
"type": "text_rule",
"text_rule_params": {
"threshold": 1,
"time_window": "1h",
"harm_labels": ["INSULT"],
"severity": "HIGH"
}
}
],
"action": {
"type": "flag_user"
}
}threshold: 1 fires on the first matching message. Increase it to require a repeated pattern. The same moderation_rule.triggered event is sent when the rule fires.
Subscribe to the event
Configure the hook in the dashboard (Moderation → Preferences → Webhook & SNS/SQS) or via event_hooks on the app:
{
"hook_type": "webhook",
"enabled": true,
"product": "moderation",
"event_types": ["moderation_rule.triggered"],
"webhook_url": "https://example.com/moderation/rules"
}product must be moderation (or all). If event_types omits moderation_rule.triggered, the event is dropped.
Event payload
{
"type": "moderation_rule.triggered",
"created_at": "2026-09-14T11:00:54.928472211Z",
"rule": {
"id": "5423c4d3-d4f0-4264-85a9-e176c9915477",
"name": "High-profile harms",
"type": "content",
"description": "Notify when selected harm labels are detected"
},
"entity_id": "85f3c2a1-9c4e-4b1d-8a77-2f0e91c4d123",
"entity_type": "stream:chat:v1:message",
"user_id": "user-42",
"triggered_actions": ["webhook_only"]
}| Field | Description |
|---|---|
type |
Always moderation_rule.triggered. |
created_at |
When the rule fired. |
rule.id |
Rule ID. |
rule.name |
Rule name. |
rule.type |
content, user, or call. |
rule.description |
Rule description. |
entity_id |
ID of the entity that triggered the rule. For chat this is the message ID. For Feeds v3 this is the activity or comment ID. For calls this is the call CID. |
entity_type |
Entity type. Common values: stream:chat:v1:message, stream:feeds:v3:activity, stream:feeds:v3:comment, stream:v1:call. |
user_id |
ID of the user who created the entity. |
triggered_actions |
Actions the rule executed, for example ["webhook_only"] or ["flag_user"]. |
review_queue_item_id |
Review-queue item ID when one is associated with the event. |
violation_number |
Present on call rules with action sequences. |
matched_contents |
Present on aggregation rules (keyframes, closed captions, keyframe OCR) when the request supplied content_ids. Each entry includes type, id, published_at, and the classifications or severity for that item. |
One event is sent per rule per entity (and per violation_number on call rules).
Related events
| Event | When it fires |
|---|---|
moderation_rule.triggered |
A Rule Builder rule matched. Use this for label-scoped or condition-scoped notifications. |
review_queue_item.new |
A review-queue item is created. The payload includes the item, flags, and the enriched entity (message, activity, comment, or call). |
review_queue_item.updated |
An existing item receives more flags or a moderator action. |
moderation_check.completed |
A moderation check finished, including when the recommended action is keep. |
See Webhooks for the full payload of each event.