# Webhooks

> Push notifications to your destination site for instant publishing. HMAC signing, payload schema, retry policy.

Webhooks let your destination site **revalidate the second a post goes live**, instead of waiting for the next ISR / SWR cycle.

> Webhooks are optional. Without them, your destination still picks up new posts on its next revalidation interval (default 5 minutes).

## When Mentionwell fires

| Event | Path Mentionwell POSTs to your destination |
|---|---|
| Post published | `POST {destinationBaseUrl}{revalidateEndpoint}` |
| Post updated | same |
| Post deleted | same (with `{ deleted: true }`) |

The path is configurable in **Settings → Delivery → Revalidate Endpoint**. Default: `/api/mentionwell-revalidate`.

## Payload

```json
{
  "event": "post.published",
  "siteSlug": "your-site-slug",
  "post": {
    "slug": "five-questions-to-ask-before-you-buy",
    "title": "Five Questions to Ask Before You Buy",
    "publishedAt": "2026-04-21T15:00:00.000Z"
  }
}
```

`event` is one of `post.published`, `post.updated`, or `post.deleted`. For deletions, `post.publishedAt` is the timestamp of the original publish. Always parse the body as raw text first (you need the exact bytes for signature verification), then `JSON.parse` it.

## Signature

Every request includes a signature header:

```text
X-Mentionwell-Signature: <hex hmac-sha256(rawBody, MENTIONWELL_WEBHOOK_SECRET)>
```

Verify it before trusting the payload.

### Node.js

```ts
import crypto from "node:crypto";

export async function POST(req: Request) {
  const raw = await req.text();
  const sig = req.headers.get("x-mentionwell-signature");
  const expected = crypto
    .createHmac("sha256", process.env.MENTIONWELL_WEBHOOK_SECRET!)
    .update(raw)
    .digest("hex");
  if (sig !== expected) return new Response("invalid signature", { status: 401 });
  const { event, siteSlug, post } = JSON.parse(raw);
  // post.slug, post.title, post.publishedAt
  // event ∈ { "post.published", "post.updated", "post.deleted" }
  // … revalidate /blog and /blog/<post.slug>
}
```

### Python

```python
import hmac, hashlib

def verify(raw_body: bytes, signature: str, secret: str) -> bool:
    expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature)
```

## Delivery & retries

Webhook delivery happens once at publish time. A delivery is considered successful if your endpoint returns a 2xx response within ~10 seconds.

If it fails (non-2xx, network error, or timeout), the post still publishes inside Mentionwell and remains live on the reader API — only the webhook fan-out is missed. The post's `deliveryStatus` flips to `failed` and the last error message is stored on the post; the dashboard surfaces these in the Articles list with a "Push failed" badge.

To retry a failed delivery: re-publish the post from the dashboard (Unpublish → Republish). Future versions will add automatic retries with backoff — track via the `deliveryStatus` field if you need to backfill manually.

> If your endpoint is reliably down for &gt; 10 seconds, prefer pull mode: your destination polls the reader API on its own ISR/SWR cycle and can never miss a publish.

## Where to find the secret

Open **Ship → Webhook**. The secret looks like `whsec_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx`.

> The webhook secret is **different** from the API key. The API key authorises reading posts; the webhook secret authenticates outbound pushes from Mentionwell.


---

Canonical URL: https://mentionwell.com/docs/webhooks
Live HTML version: https://mentionwell.com/docs/webhooks
Section: API reference
Site index for AI ingestion: https://mentionwell.com/llms.txt
Full reference: https://mentionwell.com/llms-full.txt
