Event types
The twelve events Infrared sends, when each one fires and what its data holds, with a real payload for moderations, queue items, authors and actions.
An event is one thing that happened in a project: a moderation finished, a queue item was decided, an author was suspended, an action ran. There are twelve types. An endpoint receives the ones it subscribed to, and your handler branches on type.
Every event has the same envelope. Only data changes with the type.
{
"id": "evt_3zOw19OFNY90sutpFKkCw1",
"type": "queue_item.rejected",
"created_at": "2026-10-08T08:47:47.909Z",
"api_version": "2026-09-01",
"data": {
"action": "reject",
"source": "api",
"queue_item": {
"id": "qi_2j7nkTPbA3YvLz4qpxmzPk",
"state": "resolved",
"object": "queue_item",
"resolution": "rejected",
"resolved_at": "2026-10-08T08:47:47.630Z",
"moderation_id": "mod_1f4LShzWx2qKdBwHC7u5kM"
},
"resolution": "rejected",
"moderation_id": "mod_1f4LShzWx2qKdBwHC7u5kM"
}
}idstringevt_and 22 characters. The same on every retry, on a replay and on every endpoint that receives the event. Use it to handle an event once.typestringOne of the twelve types below.
created_atstringWhen the event was recorded, in UTC.
api_versionstringThe version the payload follows:
2026-09-01.dataobjectWhat happened. Its keys depend on
type.
The examples on this page are real payloads, in the order their keys arrived. Inside data that is storage order, shortest names first, not the order the same object has in an API response. Read keys by name and never by position.
The twelve events
| Event | Sent when | data |
|---|---|---|
moderation.completed | A moderation finishes. Its status is completed, partial or invalid. | moderation |
moderation.failed | A run fails and the moderation ends with the status failed. | moderation |
queue_item.created | A moderation is sent to review and enters the queue. | queue_item, moderation_id |
queue_item.allowed | A queue item is resolved with the allow action. | queue_item, moderation_id, resolution, action, source |
queue_item.rejected | A queue item is resolved with the reject action. | The same five. |
queue_item.actioned | A queue item is resolved with an action of your own. | The same five. |
queue_item.reopened | A resolved item is reopened. | queue_item, moderation_id |
author.blocked | An author's status becomes blocked. | author, previous_status, reason, until, source |
author.suspended | An author's status becomes suspended. | The same five. |
author.enabled | An author's status becomes enabled, by a request or because a suspension reached its end. | The same five. |
author.trust_level_changed | An author's trust level changes, from their history or from an override. | author, previous_trust_level, trust_level, source |
action.executed | An action runs, from the API or the dashboard. | action, moderation_id, author_id, content_id, queue_item_id, value, source, executed_at |
An endpoint with an empty event_types list receives all of them. Ignore a type your handler does not know, so that a new one never breaks it.
Moderation events
data.moderation is the moderation object, the same body the API returned for it.
{"id": "evt_0GQHhLiYtDqcwozYf0gbqe","type": "moderation.completed","created_at": "2026-10-08T08:48:16.317Z","api_version": "2026-09-01","data": {"moderation": {"id": "mod_2wCRB1Sodc8XV83zmnJ6x6","rule": { "id": "r6", "name": "Links from new accounts", "action": "review" },"test": true,"type": "text","usage": { "cached": false, "checks": 0 },"author": null,"object": "moderation","status": "completed","details": {"guideline/no-off-site-deals": {"labels": ["confidence:100"],"matches": [{"field": null,"label": "evidence","value": "Send the deposit to my bank account and I will hold it for you."}],"explanation": "The seller explicitly requests a deposit to their bank account, which is a clear attempt to move the transaction off-platform."}},"dry_run": false,"flagged": true,"reasons": ["trigger:links_contact:flag","trigger:guideline/no-off-site-deals:flag","rule:r6"],"language": "en","metadata": null,"policies": { "toxicity": 0, "links_contact": 0.7, "guideline/no-off-site-deals": 1 },"severity": "critical","template": { "slug": "listing", "version": 1 },"triggered": ["links_contact", "guideline/no-off-site-deals"],"content_id": null,"context_id": null,"created_at": "2026-10-08T08:48:12.475Z","latency_ms": 2500,"completed_at": "2026-10-08T08:48:15.121Z","recommendation": "review"}}}- Read
statusbeforerecommendation.moderation.completedis sent for every moderation that finishes, including one that endspartialbecause a policy did not answer, and one that endsinvalidbecause the submission failed the template's validation. Statuses and failures says what each means. moderation.failedcarries the moderation withstatus: "failed", anerrorobject whosecodeispipeline_error, no scores and arecommendationofnull. Treat the content as not screened.- A whole form is one event. The moderation of a form carries the result of each field under
fields. Each item of a batch is its own moderation and its own event. - The event is sent whether or not the request waited. A text answered in the same response sends it too, so one handler can serve both paths.
The payload quotes what the policies matched. The event log keeps it for 30 days on every plan, whatever the project's retention is.
Queue item events
data.queue_item is the queue item as it stands after the move. moderation_id repeats the id of the moderation that the item's summary describes.
{"id": "evt_0TzNdRH4XaX8ncewSgoZDH","type": "queue_item.created","created_at": "2026-10-08T08:48:16.033Z","api_version": "2026-09-01","data": {"queue_item": {"id": "qi_6B6ffFjJmuKtYExjbxpxgu","state": "pending","object": "queue_item","reason": "rule","summary": {"type": "text","author": null,"scores": { "toxicity": 0, "links_contact": 0.7, "guideline/no-off-site-deals": 1 },"dry_run": false,"preview": "Solid oak desk, barely used. Send the deposit to my bank account and I will hold it for you.","language": "en","severity": "critical","template": "listing","triggered": ["links_contact", "guideline/no-off-site-deals"],"content_id": null},"assignee": null,"language": "en","priority": "urgent","severity": "critical","author_id": null,"created_at": "2026-10-08T08:48:15.782Z","project_id": "prj_0TBAgHGtdMketrbltGFHuk","resolution": null,"sla_due_at": null,"updated_at": "2026-10-08T08:48:15.782Z","assigned_at": null,"reopened_at": null,"resolved_at": null,"resolved_by": null,"template_id": "tpl_64mYq4nD3NXNfu0gcdaMZa","moderation_id": "mod_2wCRB1Sodc8XV83zmnJ6x6"},"moderation_id": "mod_2wCRB1Sodc8XV83zmnJ6x6"}}This item belongs to the moderation in the example above it. The moderation was made with a test key, and the queue item says nothing about that: a queue event carries no test flag, so look the moderation up by moderation_id when you need to know.
The three events that resolve an item add three keys, as in the example at the top of the page.
| Key | Value |
|---|---|
resolution | allowed, rejected, or action: followed by the action's key, for example action:request_edits. |
action | The key of the action that resolved the item: allow, reject or your own. |
source | api or dashboard. |
queue_item.resolved_by is the id of the member who decided in the dashboard. It is null when an API key did.
Resolving an item that is already resolved answers 200 and changes nothing. No queue_item event is sent for it, but the action runs again and action.executed is sent again.
Author events
data.author is the author object after the change.
{
"id": "evt_4GJX2vnoy66uDEpBcmeYqZ",
"type": "author.suspended",
"created_at": "2026-10-08T08:49:05.850Z",
"api_version": "2026-09-01",
"data": {
"until": "2026-10-13T00:00:00.000Z",
"author": {
"id": "aut_6xtGWa266HzKklUSuX97vC",
"object": "author",
"status": "suspended",
"created_at": "2026-10-08T08:47:22.833Z",
"project_id": "prj_0TBAgHGtdMketrbltGFHuk",
"updated_at": "2026-10-08T08:49:05.663Z",
"external_id": "user_8841",
"trust_level": "new",
"display_name": "Jordan Lee",
"status_until": "2026-10-13T00:00:00.000Z",
"status_reason": "Three rejected listings in one day",
"trust_override": null,
"trust_level_automatic": "new"
},
"reason": "Three rejected listings in one day",
"source": "api",
"previous_status": "enabled"
}
}previous_statusis the status before the change. Setting an author to the status they already have sends nothing.untilis set only for a suspension with an end date.sourceisapi,dashboardorrule.rulemarks a suspension that lifted itself: within 15 minutes of its end date Infrared sendsauthor.enabledwithreason: "suspension expired".author.trust_level_changedcarriesprevious_trust_levelandtrust_level, each one ofnew,basic,regular,establishedandtrusted. Itssourceisautomaticafter a moderation andmanualafter an override.
Blocking or suspending an author also drops their automatic trust level to new. No author.trust_level_changed is sent for that drop: read data.author.trust_level in the status event.
Action events
action.executed is sent every time an action runs, whatever its target.
{"id": "evt_4m24Cq6qE5xjatO20hxDK6","type": "action.executed","created_at": "2026-10-08T08:48:22.365Z","api_version": "2026-09-01","data": {"value": { "reason": "off_platform_link", "message": "Remove the link from the description." },"action": {"id": "act_2BdP4rjezzQ5PTND66nyLi","key": "request_edits","name": "Request edits","object": "action","target": "content","enabled": true,"built_in": false,"position": 5,"created_at": "2026-10-08T08:44:38.841Z","project_id": "prj_0TBAgHGtdMketrbltGFHuk","updated_at": "2026-10-08T08:44:38.841Z","description": "Send the content back to its author with what to change. Your webhook receives the message.","value_schema": null,"accepts_value": true,"queue_behavior": "resolve","visible_in_dashboard": true},"source": "api","author_id": null,"content_id": "listing_20932","executed_at": "2026-10-08T08:48:22.177Z","moderation_id": "mod_6IYSa2qBKpMi4Xnxm1jkOB","queue_item_id": "qi_0eietVVV87wOZub5qkiPfU"}}actionis the whole action object, not only its key.content_idis the id you sent with the moderation.author_idis Infrared's id of the author, starting withaut_, not the id you use for them. It isnullfor an action on content.valueis whatever JSON the run carried, unchecked. Validate it in your handler.- The note a moderator typed and the person who ran the action are not in the event.
One request, several events
Some requests send more than one event, each to be handled on its own.
| What happened | Events, in the order they are recorded |
|---|---|
| A moderation is sent to review | author.trust_level_changed when the level moved, queue_item.created, then moderation.completed |
| A queue item is resolved | queue_item.allowed, queue_item.rejected or queue_item.actioned, then action.executed |
The block_author, suspend_author or enable_author action runs | The author.* event, then action.executed |
Deliveries run side by side and a failed one is retried later, so events can arrive in another order. When order matters, compare created_at, or read the current state from the API.
Test keys and dry runs
A test key and a dry run both send events. Tell them apart in the payload.
| Traffic | Moderation and queue events | Automatic trust change |
|---|---|---|
| Live key | Sent | Sent when the level moves |
| Test key | Sent, with test: true in the moderation | Not sent: author history is not updated |
| Dry run | Sent, with dry_run: true in the moderation and reason: "dry_run" on the queue item | Not sent |
| Template test | Nothing is sent | Nothing is sent |
Everything after that is the same for all of them. Resolving a queue item that a test moderation created sends the same events as any other.
Read past events
Every event is kept for 30 days, whether or not the project has an endpoint. List events returns them newest first: filter with type, and page with limit and cursor as on every list.
curl "https://api.useinfrared.com/v1/webhooks/events?type=moderation.completed&limit=1" \
-H "Authorization: Bearer $INFRARED_KEY"const response = await fetch("https://api.useinfrared.com/v1/webhooks/events?type=moderation.completed&limit=1", {
headers: { Authorization: `Bearer ${process.env.INFRARED_KEY}` },
});
const events = await response.json();import os
import requests
response = requests.get(
"https://api.useinfrared.com/v1/webhooks/events",
headers={"Authorization": f"Bearer {os.environ['INFRARED_KEY']}"},
params={
"type": "moderation.completed",
"limit": 1,
},
)
events = response.json(){"object": "list","data": [{"object": "webhook_event","id": "evt_0mEfEDs3uCiqRA34OUEL5C","type": "moderation.completed","created_at": "2026-10-08T08:55:03.172Z","api_version": "2026-09-01","data": {"moderation": { "id": "mod_7ZgXCne4LiwQrsodlx4R8U", "object": "moderation", "status": "completed" }}}],"has_more": true,"next_cursor": "WyIyMDI2LTEwLTA4VDA4OjU1OjAzLjE3MloiLCIxOTkwMWIzZS0wYTBiLTQ0MzEtYTlhMy0xZGQ5ZGMwZjI4ODYiXQ"}Use the list to catch up after an outage, or after an endpoint was disabled: those events were never sent to it and cannot be replayed. The dashboard has no screen for the event log.
Next
- Verify signaturesEvery delivery is signed with the endpoint's secret. Check the signature and its timestamp before you trust the body, with code you can copy.
- Deliveries, retries and replayA delivery is one event sent to one endpoint. What counts as delivered, how a failure is retried for about an hour, and how to send a delivery again.
- Review queueContent your rules send to review waits in the queue until a person, or your own code, allows it, rejects it or runs another action on it.
- ActionsAn action is what a decision turns into. Allow, reject, change an author's status or define your own, and act on the webhook each run sends.
Something missing or unclear on this page? Write to support@useinfrared.com.