Webhook Integration: Publish Content Agent Drafts to Any Stack
Connect a signed webhook so the ReddGrow content agent can publish to a custom stack, headless CMS, or Squarespace site
The Webhook integration is the escape hatch for anything ReddGrow doesn't support natively: a custom Next.js site, Sanity, Contentful, or any other stack with an HTTPS endpoint you control. Instead of calling a platform's API directly, ReddGrow sends your endpoint an HMAC-signed POST request describing what to publish, update, or remove, and you decide how that turns into a real page.
This is also the supported route for Squarespace. Squarespace doesn't offer a public API for creating blog posts in any form, so a webhook to your own middleware is the only way to get content agent output onto a Squarespace site.
Requirements
- An HTTPS endpoint you control that can receive POST requests
- The ability to verify an HMAC-SHA256 signature on the raw request body
- A signing secret you choose yourself (any strong random string)
What ReddGrow sends
Every request carries two headers:
| Header | Value |
|---|---|
X-ReddGrow-Signature | sha256=<hex HMAC of the raw request body> |
X-ReddGrow-Event | The event name, e.g. content.publish |
Events:
content.publish: a new draft is ready to go livecontent.update: an existing published item changedcontent.meta_patch: only the title/meta description changed (used for periodic content refreshes)content.unpublish: an item should be taken down or restored to its previous title/descriptioncontent.append: for FAQ blocks, append a fragment to an existing page instead of creating a new onecontent.remove_append: remove a previously appended FAQ block from a pagecontent.ping: a connectivity check, fired by the Test button on the integration card
For content.publish, content.update, content.meta_patch, and content.unpublish, the payload looks like:
{
"event": "content.publish",
"draft": {
"title": "...",
"slug": "...",
"markdown": "...",
"html": "...",
"meta_description": "...",
"schema_jsonld": { "...": "..." },
"target_url": "...",
"previous_title": "...",
"previous_meta_description": "..."
},
"sent_at": "2026-07-28T12:00:00.000Z"
}For content.append, the payload is flatter. It carries only what's needed to insert a fragment into an existing page:
{
"event": "content.append",
"target_url": "https://example.com/faq",
"html": "<section>...</section>",
"markdown": "...",
"title": "...",
"sent_at": "2026-07-28T12:00:00.000Z"
}content.ping sends just { "event": "content.ping", "sent_at": "..." }.
Verify the signature
Compute the HMAC over the exact raw bytes of the request body. Re-serializing the JSON before hashing will produce a different signature and reject valid requests.
import { createHmac, timingSafeEqual } from 'crypto';
function isValidSignature(rawBody, signatureHeader, secret) {
const expected = createHmac('sha256', secret).update(rawBody).digest('hex');
const provided = (signatureHeader || '').replace('sha256=', '');
const expectedBuf = Buffer.from(expected, 'hex');
const providedBuf = Buffer.from(provided, 'hex');
return expectedBuf.length === providedBuf.length && timingSafeEqual(expectedBuf, providedBuf);
}Make sure your framework gives you the raw body (e.g. express.raw({ type: 'application/json' })) before any JSON-parsing middleware touches it.
Expected response
Return a 2xx status once you've accepted the request. You can optionally include a JSON body with the canonical location ReddGrow should remember:
{ "external_id": "your-internal-id", "published_url": "https://example.com/blog/my-post" }If you omit this, ReddGrow falls back to defaults: external_id defaults to the draft's ReddGrow ID (or the target_url for appends), and published_url defaults to <your Site URL>/<slug> (or the target_url for appends).
Test button behavior
Clicking Test on the Webhook card sends a content.ping event to your endpoint. Any 2xx response marks the connection healthy; anything else, or a network failure, is reported back as an error on the card.
Connect in ReddGrow
- Go to Settings → Integrations
- Under Website publishing, find the Webhook card and click Connect
- Fill in the form:
- Endpoint URL: the HTTPS URL ReddGrow should POST to
- Signing secret: the secret your endpoint will use to verify signatures
- Click Connect Webhook
If another platform is already connected, ReddGrow warns you that connecting Webhook will replace it. Only one website can be connected at a time.
Troubleshooting
The Test button fails
Your endpoint must return a 2xx status. Check that the URL is publicly reachable over HTTPS and that no firewall or auth layer is blocking the request before it reaches your handler.
Signature verification fails on requests that look correct
This is almost always a raw-body mismatch. If your framework parses the JSON body before your signature check runs, the bytes you re-serialize to verify won't match the bytes ReddGrow signed. Verify against the untouched raw body.
Squarespace content isn't appearing on my site
The webhook only delivers the content payload. Turning that into a live Squarespace page is on your middleware, since Squarespace has no publishing API. Confirm your endpoint is actually creating/updating the Squarespace post, not just accepting the request.
FAQ blocks aren't appending where expected
content.append and content.remove_append use target_url to identify the page to modify, not slug. Make sure your handler branches on the event field, since the append payload shape is different from the publish/update shape.
ReddGrow shows the wrong published URL for an item
Your endpoint's 2xx response isn't returning published_url (and external_id), so ReddGrow is falling back to its default URL guess. Return both fields in your response body to keep them accurate.
Related: see the ReddGrow App guide for how campaigns, drafts, and the platform's automation pipeline fit together.