A simple webhook and review integration pattern for small teams
Most smaller teams do not need a giant moderation operations platform. They need a clean way to surface the right decisions, notify the right people, and keep a human in the loop without building an internal control plane before the product has earned it.
One of the easiest ways to overcomplicate moderation is to assume you need a huge internal system before you can operate it properly. Most smaller teams do not. They need a flow that is dependable, visible, and lightweight enough that someone will actually maintain it.
A practical pattern is to let the API make the first decision, then use webhooks and the review queue to route only the cases that deserve operational attention. That keeps your product flow fast and your moderation workflow small enough to remain believable.
A workable pattern
The key thing webhooks give you here is push rather than pull. You do not want moderators or internal systems polling the API every few seconds to check whether anything interesting has happened. That creates unnecessary load, introduces lag, and means you are always working with slightly stale data. Instead, you want events delivered to the right place the moment they happen — which is exactly what webhook delivery is designed for.
In practice, the flow looks like this:
- Call the moderation endpoint synchronously during your normal product flow.
- Allow safe content and block clearly unsafe content immediately, before the response goes back to the user.
- Send
revieworblockevents to a configured webhook endpoint. - Forward those events to a team channel, internal tool, or lightweight dashboard process — whatever is appropriate for the event type.
- Resolve borderline items through the review queue when a human decision is actually needed.
That is the full cycle. The synchronous call keeps the product experience fast. The webhook delivery handles everything that does not need to happen in the same request.
What the webhook payload contains
A webhook is only useful if the receiver can do something meaningful with it. That means the payload needs to carry enough information for the receiving system to decide how to handle the event — without having to make another API call to find out what happened.
A typical AegisCore webhook payload includes:
- the event type, such as
moderation.blockedormoderation.review - the moderation score and the action taken
- the matched categories that drove the result — hate speech, self-harm, spam, and so on
- a reference ID you can use to retrieve the full item from the API if you need more detail
- a timestamp for the event
With that information in hand, the receiver can write to a database, post a formatted alert, or enqueue a task — all without a follow-up request. That is the right design. Webhook payloads that contain only an ID and force the receiver to call back for the actual data introduce unnecessary latency and create a second failure point.
Verifying signatures
Once you have a webhook endpoint running, you need to make sure it only processes requests that actually came from AegisCore. An endpoint URL is not a secret. If someone discovers it, they can post arbitrary payloads to it and potentially trigger your internal systems with fabricated events.
AegisCore uses a shared secret to produce an HMAC signature over the raw request body. That signature is included in the request headers. Your receiver should verify it before doing anything with the payload — check the signature, and if it does not match, discard the request and return a 4xx response.
This is not optional. An unverified webhook endpoint is one that anyone who knows the URL can influence. The verification step takes a few lines of code and prevents a straightforward class of spoofing attack. Build it in from the start.
Why this is useful
This pattern keeps the main product flow simple while still giving the team an operational safety valve. It also avoids one of the most common failure modes in early moderation tooling: forcing moderators or support staff to poll manually for events that should have been pushed to them in the first place.
If only a small fraction of cases need attention, then that is all the team should see. Dumping every decision into Slack, Discord, or an internal dashboard just creates alert fatigue and teaches people to ignore the signals that matter.
There is also a decoupling benefit that is easy to overlook. Because the webhook delivery happens asynchronously, your product flow does not have to wait for downstream systems to respond. The moderator notification, the internal tooling update, and the audit log write all happen after the fact. That is the right architecture — the user's experience should not be held up waiting for a Slack message to send or a database row to be written somewhere in an internal tool. Keep the synchronous path lean, and let the asynchronous delivery handle the rest.
Where teams usually send these events
- Slack or Discord via a relay service. A lightweight serverless function or small relay service receives the webhook and posts a formatted message to the relevant channel. Typically this includes the score, the category, and a link or reference to the content — enough for someone to understand at a glance whether they need to look more closely.
- An internal backend receiver. The simplest option for teams that want to persist events in their own systems. The receiver is a plain POST endpoint that writes the event to a database table and optionally enqueues a task for async processing. No third-party dependencies, easy to reason about, straightforward to monitor.
- A small moderation tool used by support or operations staff. This is the more mature path. Rather than posting to a general channel, the webhook drives an internal dashboard where staff can see open review items, resolved decisions, and the history of past actions. It takes more to build, but it is what a moderation workflow looks like once the volume justifies something more structured than a Slack message.
For most smaller teams, the first or second option is sufficient to start. You can move toward the third as the product scales. The pattern does not change — only the destination does.
Handling failures and retries
Webhook delivery can fail. The receiver might be down, return a timeout, or respond with a 5xx error. When that happens, the event should not be silently dropped — a lost event means your team has a gap in their view of what moderation has done, which is exactly the kind of thing that causes problems later.
AegisCore queues retries with exponential backoff when delivery fails. That means a transient outage on the receiver side should not result in lost events, as long as the receiver recovers within the retry window.
On your side, it is worth implementing idempotency in the receiver. Each event has a reference ID. If the same event is delivered more than once — which can happen legitimately with retry logic — your receiver should detect the duplicate and discard it rather than processing it twice. A database upsert keyed on the event ID is usually all you need. It is a small addition that prevents double-writes, duplicate notifications, and confusing audit trail entries.
What not to do
Avoid building a system where every moderation outcome becomes a message, every message becomes a ticket, and every ticket requires a human. That looks thorough for about a week and then collapses under its own weight. A moderation workflow should help a small team stay focused, not bury them in process.
The other mistake worth calling out: do not put business logic inside the webhook receiver that belongs in the API. The receiver's job is to record the event, route it to the right place, and send any necessary notifications. It is not the place to re-run moderation logic, adjust scores, or try to override decisions based on local rules. When you do that, you end up with fragmented policy — different parts of the system applying different rules, producing outcomes that are hard to explain and harder to audit. Keep the moderation logic in one place. The receiver reacts; it does not decide.