Actions for your team, signed webhooks for your backend
Every action a moderator or your code runs is logged and sent to your endpoints as an event.
- On every plan
- Signed with HMAC-SHA256
A moderator runs Request edits on this item.
Film camera, barely used. Call me on +44 7700 900123
listings · English · listing_48213
- Block author
- Suspend author
- Enable author
- Request edits
- Assign to me
- Open in live console
request_editsjust nowContent mod_1B7l…3wVuDashboardreject1 min agoContent mod_1B7l…3wVuDashboardallow4 min agoContent mod_6Wn3…Pq7aAPIblock_author1 hour agoAuthor aut_2Hd9…Xk4mDashboard
POST /hooks/infraredInfrared-Signature: t=1790863420,v1=<64 hex characters>Infrared-Event: action.executedInfrared-Delivery: whd_3k9pXa7Fm2Qw8Ls4Td6VbN{
"type": "action.executed", …
"data": {
"action": { "key": "request_edits", … }, …
}
}{
"type": "action.executed", …
"data": {
"action": {
"key": "request_edits", …
}, …
}
}Sample data
Five built-in actions, and your own
Allow, Reject and the three author actions change Infrared's own records. An action you add is logged and sent as an event, and your backend does the rest.
An action can take a valueA reason as text, or JSON of your own. It is kept in the action log and sent in the webhook.
From a queue item or the APIA moderator runs it on the item, or your code calls
POST /v1/actions/:key/execute.
| Name | Key | Target | Queue behaviour | Visible in dashboard | Enabled |
|---|---|---|---|---|---|
| AllowBuilt-inallow | allow | Content | Resolve the queue item | ||
| RejectBuilt-inreject | reject | Content | Resolve the queue item | ||
| Block authorBuilt-inblock_author | block_author | Author | Keep it in the queue | ||
| Suspend authorBuilt-insuspend_author | suspend_author | Author | Keep it in the queue | ||
| Enable authorBuilt-inenable_author | enable_author | Author | Keep it in the queue | ||
| Request editsrequest_editsAsk the seller to fix the listing | request_edits | Content | Resolve the queue item |
Recent executions
Each execution is logged and sent to your webhooks as action.executed.
| Action | Target | Source | Note | Actor | When |
|---|---|---|---|---|---|
| request_edits | Content mod_1B7l…3wVu | Dashboard | Second time this week | usr_6Tz2…kQ8m | 2 min ago |
| suspend_author | Author aut_5Kc2…RnTq | API | No note | API key | 1 hour ago |
Sample data
Twelve webhook events tell your backend what changed
Each endpoint receives every event type, or only the ones you tick.
Kept for 30 daysRead events and deliveries back from the API.
Endpoints per project1 on Free, 3 on Starter, 10 on Growth and 50 on Business, each a public https URL.
moderation.completedmoderation.failed
author.blockedauthor.suspendedauthor.enabledauthor.trust_level_changed
queue_item.createdqueue_item.allowedqueue_item.rejectedsent 1queue_item.actionedqueue_item.reopened
action.executedsent 2
The open item is resolved as Rejected, then the run is reported.
Sample dataVerify the webhook signature, then trust the body
The dashboard prints this function on each endpoint's page, ready to paste into a Node.js receiver.
POST /hooks/infraredInfrared-Signature: t=1790863420,v1=<64 hex characters>Infrared-Event: action.executedInfrared-Delivery: whd_3k9pXa7Fm2Qw8Ls4Td6VbNInfrared-Api-Version: 2026-09-01{
"id": "evt_2Vd8RkQ0mZx4TnL7sYc1Wb",
"type": "action.executed",
"created_at": "2026-10-01T14:03:40.000Z",
"api_version": "2026-09-01",
"data": {
"action": { "key": "request_edits", … },
"moderation_id": "mod_1B7lY4kzM9n2Qx8sRt3wVu",
"author_id": null,
"content_id": "listing_48213",
"queue_item_id": "qi_0pX3hN9vKe6Ju2Ad5GwM8r",
"value": { "reason": "Phone number in the description" },
"source": "dashboard",
"executed_at": "2026-10-01T14:03:40.000Z"
}
}const crypto = require("crypto");
// Infrared-Signature: t=<unix>,v1=<hex>// v1 = HMAC-SHA256(secret, "<t>.<raw body>")function constructEvent(rawBody, header, secret, toleranceSeconds = 300) { const parts = Object.fromEntries(header.split(",").map((p) => p.split("="))); const t = Number(parts.t); if (!t || !parts.v1) throw new Error("malformed signature"); if (Math.abs(Date.now() / 1000 - t) > toleranceSeconds) { throw new Error("signature too old"); } const expected = crypto .createHmac("sha256", secret) .update(`${t}.${rawBody}`) .digest("hex"); if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1))) { throw new Error("bad signature"); } return JSON.parse(rawBody);}Every attempt is signed again with a new t. The whsec_ secret is shown once when the endpoint is created, and once more when you rotate it.
An endpoint that keeps failing is disabled
After 50 failed attempts in a row Infrared disables the endpoint and emails the organisation's owners and admins.
Eight attempts, then it stopsA 4xx response other than 408 or 429 ends the delivery at once.
Replay once it is enabledOn an active endpoint the same event goes out again, with eight attempts of its own.
POSThttps://example.com/hooks/infrared- Event
moderation.completed- Infrared-Delivery
whd_9Qb4TnH6yWs1Kd8Xc3LmRv- Status
- Pending
- Response
- 503
- Attempts
- 1 of 8
- Attempt 1answered 5030:00first
- Attempt 2Next0:30+30 s
- Attempt 31:30+1 min
- Attempt 43:30+2 min
- Attempt 57:30+4 min
- Attempt 615:30+8 min
- Attempt 731:30+16 min
- Attempt 863:30+32 min
Every failed attempt counts, retries included. One 2xx response sets the count back to zero.
Sample data

Start moderating your users' content
Create an API key, send your first request and get a verdict back.