Skip to content

Webhooks

Infrared posts a signed event to your endpoint when a moderation finishes, a queue item is decided or an author changes. Add an endpoint and receive it.

A webhook tells your backend that something happened, so your backend does not have to ask. An image finished screening. A moderator rejected a listing in the review queue. An author was suspended. Each of these is an event, and Infrared sends it as a signed JSON POST to every endpoint of the project that asked for it.

You need one as soon as an answer arrives after the request. Images are screened in the background, and a decision in the review queue can come hours later. Infrared never publishes or removes anything in your product, so the webhook is how a decision reaches the code that does.

One delivery
Sample data
InfraredAn eventmoderation.completedevt_2uhhIo2wErQojIHSPaTfHE
Infrared sends your endpoint this request: POST https://hooks.example.com/infraredInfrared-Signature: t=…,v1=…Infrared-Event: moderation.completedInfrared-Delivery: whd_0oBGD4cLwRR1dYwd5ow6oW
Your endpoint
  • Verify the signature
  • Answer 2xx within 10 seconds
  • Handle the event once, by its id
  1. 2xx

    Delivered:succeeded

  2. 5xx408429a redirect, a timeout or a network error
    Retried8 attempts over about an hour
  3. Any other 4xx

    Not delivered:failedNo retry

Sample data

One event from Infrared to your endpoint, and what a failed attempt leads to.

Add an endpoint

An endpoint is a URL of yours, the events it wants and a signing secret. Add one with a request, using a key that has the write scope:

curl https://api.useinfrared.com/v1/webhooks \
  -H "Authorization: Bearer $INFRARED_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://hooks.example.com/infrared",
    "description": "Syncs verdicts to the CMS",
    "event_types": [
      "moderation.completed",
      "queue_item.allowed",
      "queue_item.rejected"
    ]
  }'
201 CreatedJSON
{
  "object": "webhook_endpoint",
  "id": "whe_0dFoExHEccOjnJ7ZHHwXsq",
  "project_id": "prj_0TBAgHGtdMketrbltGFHuk",
  "url": "https://hooks.example.com/infrared",
  "description": "Syncs verdicts to the CMS",
  "event_types": ["moderation.completed", "queue_item.allowed", "queue_item.rejected"],
  "status": "active",
  "api_version": "2026-09-01",
  "consecutive_failures": 0,
  "disabled_at": null,
  "disabled_reason": null,
  "last_delivery_at": null,
  "last_success_at": null,
  "created_at": "2026-10-08T08:53:38.326Z",
  "updated_at": "2026-10-08T08:53:38.326Z",
  "secret": "whsec_Zk3pQ8vT1mX6cR9bW2nH5yJ7dL0sF4gA8uE3tK6q" // only in this response
}
urlstringRequired

Where the events go. Up to 2,048 characters. The rules are under The endpoint URL.

descriptionstring

A note for your team, up to 500 characters.

event_typesstring[]Default []

The event types to deliver. An empty list means every event, including types added later.

secret is the signing secret, the key your endpoint verifies each delivery with. Infrared stores it encrypted and returns it in this response only. Keep it where you keep other secrets.

This call ignores the Idempotency-Key header. Sent twice, it creates two endpoints, and each one delivers every event.

In the dashboard

  1. Open Webhooks

    Open Webhooks in the project and choose Add endpoint.

  2. Enter the URL and pick the events

    Fill in Endpoint URL, and a Description if it helps your team. Under Events, tick the events this endpoint wants, or leave every box unchecked to receive every event.

  3. Copy the signing secret

    Choose Add endpoint. The Signing secret appears once. Copy it, store it, then choose Done.

A webhook endpoint, hooks.example.com, active: its URL, the events it is subscribed to, here every event, and the format of the signature each delivery carries.
Dashboard, WebhooksAn endpoint, open: its address, its state and the events it receives. The signature recipe and its deliveries follow on the same page.

The endpoint URL

  • It uses https.
  • It is public. localhost, a name ending in .internal and any address in a private or reserved range are refused when you save the endpoint. The check runs again on every delivery, against the addresses the name resolves to at that moment.
  • Redirects are not followed. A 3xx answer is a failed attempt.
  • A name that does not resolve yet is accepted. Its deliveries fail until it does.
422 Unprocessable EntityJSON
{"error": {"code": "validation_error","message": "url: Target resolves to a private address (localhost)","details": [{ "field": "url", "message": "Target resolves to a private address (localhost)" }],"request_id": "req_06bb4ebfad604972be886073091ee44e"}}

To receive events on your own machine, give the port a public https address with a tunnel and add that address as the endpoint.

A minimal receiver

A receiver does four things: it reads the raw body, verifies the signature, answers 2xx, and acts on an event once. constructEvent and construct_event are the functions from Verify signatures.

// receiver.js
import express from "express";
import { constructEvent, SignatureError } from "./infrared-webhooks.js";

const app = express();
const seen = new Set(); // in production, a store that outlives the process

// express.raw keeps the body as bytes. Put it on this route, before any express.json().
app.post("/webhooks/infrared", express.raw({ type: "application/json" }), (req, res) => {
  let event;
  try {
    event = constructEvent(req.body, req.get("Infrared-Signature"), process.env.INFRARED_WEBHOOK_SECRET);
  } catch (error) {
    if (error instanceof SignatureError) return res.status(400).json({ error: error.message });
    throw error;
  }

  // Answer first: a delivery times out after 10 seconds.
  res.json({ received: true });

  // The event id is the same on every retry and on a replay.
  if (seen.has(event.id)) return;
  seen.add(event.id);
  handle(event);
});

function handle(event) {
  if (event.type === "moderation.completed") {
    const { id, recommendation } = event.data.moderation;
    console.log(id, recommendation); // publish, hold or reject the content here
  }
}

app.listen(process.env.PORT ?? 3000);

Only a 2xx answer counts as delivered. A 5xx, a network error or no answer within 10 seconds is retried for about an hour. A 4xx other than 408 and 429 ends the delivery at once, which is why the receiver answers 400 only to a request it does not trust. Deliveries, retries and replay has the schedule.

No button sends a test event. To try a receiver, send a moderation with a test key: it uses no checks and produces a real moderation.completed.

Disable, enable and delete

Change an endpoint with PATCH /v1/webhooks/{id}, or from the menu on its page in the dashboard: Edit endpoint, Rotate secret, Disable endpoint, Delete endpoint. The request can change the url, the description, the event_types and the status, as Update an endpoint lists.

curl -X PATCH https://api.useinfrared.com/v1/webhooks/whe_0dFoExHEccOjnJ7ZHHwXsq \
  -H "Authorization: Bearer $INFRARED_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "status": "disabled"
  }'
200 OK, trimmedJSON
{
  "object": "webhook_endpoint",
  "id": "whe_0dFoExHEccOjnJ7ZHHwXsq",
  "status": "disabled",
  "consecutive_failures": 4,
  "disabled_at": "2026-10-08T08:55:27.374Z",
  "disabled_reason": "manual"
}
  • A disabled endpoint receives nothing. Events that happen while it is disabled are not delivered when you enable it again. They stay in the event log for 30 days, where your backend can read them.
  • Enabling is the same request with "status": "active". It also sets consecutive_failures back to 0.
  • Infrared disables an endpoint itself after 50 failed attempts in a row. disabled_reason then reads 50 consecutive failures. See When an endpoint keeps failing.
  • Deleting an endpoint stops its deliveries at once and removes its delivery log.

How many endpoints

LimitFreeStarterGrowthBusiness
Webhook endpoints, per project131050

The limit is per project and counts disabled endpoints too. One endpoint more answers 403 plan_limit.

Who can manage endpoints

In the dashboard, Owners, Admins, Project admins and Developers can add, edit, disable and delete endpoints, rotate a secret and replay a delivery. Moderators can read endpoints and their deliveries.

An API key needs the read scope to list endpoints, events and deliveries, and the write scope for everything else.

Next

Something missing or unclear on this page? Write to support@useinfrared.com.