Guide to Fourthwall Webhooks: Features and Best Practices
Fourthwall webhooks tell your systems what is happening in a creator's shop as it happens. A supporter places an order, sends a donation, buys a gift card or starts a membership, and Fourthwall posts the event to your endpoint. Creators use them to trigger stream alerts, push orders to accounting or shipping partners, and keep internal databases in step with the shop.
This guide covers how Fourthwall webhooks are configured and signed, the events and their envelope, Fourthwall's delivery limits, and the best practices for running a Fourthwall receiver in production.
What are Fourthwall webhooks?
A Fourthwall webhook subscribes an HTTPS endpoint to one or more event types for a shop. You create it in the shop dashboard or through the Platform API. Each delivery is a POST with a JSON body that carries one event.
Every shop webhook request is signed. The X-Fourthwall-Hmac-SHA256 header holds a base64 HMAC-SHA256 of the entire request body, keyed with the secret key Fourthwall assigns to your shop. Nothing else is signed: there is no timestamp and no prefix.
Fourthwall's documentation also sets out the limits: it says delivery is at least once, that ordering between topics is not guaranteed, and that "webhook delivery is not always guaranteed", so it recommends reconciliation jobs if your data must always be consistent.
Fourthwall webhook features
| Feature | Details |
|---|---|
| Configuration | Dashboard (Settings > For developers) or the Platform API |
| Subscription model | One endpoint per webhook, with a list of event types (allowedTypes) |
| Delivery | One event per HTTPS POST, JSON body |
| Event type | The type field in the body. No header carries it |
| Signature header | X-Fourthwall-Hmac-SHA256 (Platform Apps: X-Fourthwall-Hmac-Apps-SHA256) |
| Scheme | HMAC-SHA256, base64 |
| Signed content | The entire raw body |
| Secret | The shop's secret key, a UUID-shaped string used as UTF-8 text |
| Replay protection | None. Nothing time-based is signed |
| Response | Only 200 counts as delivered. Responses slower than 2000 ms may fail |
| Retries | 5 attempts with a 5-second timeout each. No interval is published |
| Guarantees | At least once, unordered between topics, and not guaranteed |
| Testing | Send test notification in the dashboard sends an event with testMode: true |
| Event log | GET /open-api/v1.0/webhook-events lists past events with their state and any delivery error |
Fourthwall publishes no source IP list for webhooks.
Common events
Fourthwall's OpenAPI document defines 21 webhook event types:
| Area | Event | Fired when |
|---|---|---|
| Orders | ORDER_PLACED | A new order is placed and paid |
ORDER_UPDATED | An order's status, shipping address or email changes | |
| Gifts | GIFT_PURCHASE | A gift card is purchased |
GIFT_DRAW_STARTED, GIFT_DRAW_ENDED | A giveaway draw starts or ends | |
| Support | DONATION | A donation is received |
THANK_YOU_SENT | A thank you message is sent to a supporter | |
| Memberships | SUBSCRIPTION_PURCHASED | A membership subscription is purchased |
SUBSCRIPTION_CHANGED | A membership changes, for example a tier change | |
SUBSCRIPTION_EXPIRED | A membership expires | |
| Catalogue | PRODUCT_CREATED, PRODUCT_UPDATED | A product is created or updated |
COLLECTION_UPDATED | A collection is updated | |
| Marketing | NEWSLETTER_SUBSCRIBED | Someone subscribes to the newsletter |
PROMOTION_CREATED, PROMOTION_UPDATED | A promotion is created or updated | |
PROMOTION_STATUS_CHANGED | A promotion's status changes, for example when it is activated or expires | |
| Carts | CART_ABANDONED_1H, CART_ABANDONED_24H, CART_ABANDONED_72H | A cart has been inactive for 1, 24 or 72 hours. The 72-hour event is the final abandonment signal |
| Apps | PLATFORM_ | A Platform App is disconnected from a shop |
Every event shares one envelope, and the event-specific object sits in data. Fourthwall's model example elides data, so here it is reduced to two fields of an ORDER_PLACED order:
{
"testMode": false,
"id": "weve_geAva6c1RAuyb9HQxbSlmA",
"webhookId": "wcon_P-VkRfmJTBaC6_Tst22cew",
"shopId": "sh_7ad0c438-beda-4779-a885-0dc325a755c1",
"type": "ORDER_PLACED",
"apiVersion": "V1",
"createdAt": "2023-07-12T15:05:11.078089+00:00",
"data": {
"id": "00aa4abd-5778-4199-8161-0b49b2f212e5",
"friendlyId": "D3XZFWPP"
}
}
id identifies the event, and Fourthwall says to treat repeated IDs as duplicates. webhookId names the webhook configuration that sent it, and shopId helps when one endpoint serves several shops. apiVersion is the data version chosen for the webhook: the model page's example shows V1_BETA and the OpenAPI schema shows V1.
The data object depends on type. An ORDER_PLACED order includes fields such as friendlyId, status, email, username, message, amounts and offers. ORDER_UPDATED carries order and an update whose own type is STATUS, SHIPPING.ADDRESS or EMAIL. A DONATION carries username, message and amounts.
Setting up Fourthwall webhooks
- In the Fourthwall dashboard, open Settings > For developers and find the secret key assigned to your shop. It looks like
e3f93c7c-c92b-4b8f-a9b1-5b70e0891abc. Store it server-side, for example asFOURTHWALL_, exactly as shown.WEBHOOK_ SECRET - Add a webhook with your HTTPS endpoint URL and select the event types it should receive.
- Click Send test notification on the webhook list. The event arrives with
testModeset totrue.
To create webhooks in code, call the Platform API with HTTP Basic Auth:
curl -u "your_username:your_password" \
-X POST "https://api.fourthwall.com/open-api/v1.0/webhooks" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/webhooks/fourthwall",
"allowedTypes": ["ORDER_PLACED", "ORDER_UPDATED", "DONATION"]
}'
The response includes the webhook's id and a secret field. Store the secret. The API also has GET /webhooks, GET /webhooks/{id}, PUT /webhooks/{id} and DELETE /webhooks/{id}.
If you build a Platform App rather than a shop integration, the app's webhooks are signed with the HMAC key from the app's settings and arrive in X-Fourthwall-Hmac-Apps-SHA256. Hookdeck's Fourthwall source type reads only X-Fourthwall-Hmac-SHA256, so it verifies shop webhooks only. See the limitations below for app webhooks.
For local development, the Hookdeck CLI (hookdeck listen 3000 fourthwall --path /webhooks/fourthwall) gives you a public HTTPS URL that forwards to your local server, with no account required.
Securing Fourthwall webhooks
Compute an HMAC-SHA256 of the raw body with the secret key as UTF-8 text, base64-encode it and compare it in constant time with X-Fourthwall-Hmac-SHA256. Don't decode the key as hex or base64, and don't parse the body before the check.
const crypto = require('crypto');
const express = require('express');
function verifyFourthwallSignature(rawBody, signatureHeader, secret) {
if (!signatureHeader || !secret) return false;
const expected = crypto.createHmac('sha256', secret).update(rawBody).digest('base64');
const received = Buffer.from(signatureHeader, 'utf8');
const computed = Buffer.from(expected, 'utf8');
return received.length === computed.length && crypto.timingSafeEqual(received, computed);
}
const app = express();
app.post('/webhooks/fourthwall', express.raw({ type: '*/*' }), (req, res) => {
const signature = req.get('x-fourthwall-hmac-sha256');
if (!verifyFourthwallSignature(req.body, signature, process.env.FOURTHWALL_WEBHOOK_SECRET)) {
return res.sendStatus(401);
}
const event = JSON.parse(req.body.toString('utf8'));
// Route on event.type, deduplicate on event.id, then process asynchronously.
res.sendStatus(200);
});
The same check in Python:
import base64
import hashlib
import hmac
import os
def verify_fourthwall_signature(raw_body: bytes, signature_header, secret: str) -> bool:
if not signature_header or not secret:
return False
digest = hmac.new(secret.encode("utf-8"), raw_body, hashlib.sha256).digest()
expected = base64.b64encode(digest).decode("utf-8")
return hmac.compare_digest(expected.encode("utf-8"), signature_header.encode("utf-8"))
# In FastAPI: raw_body = await request.body()
# verify_fourthwall_signature(raw_body, request.headers.get("x-fourthwall-hmac-sha256"),
# os.environ["FOURTHWALL_WEBHOOK_SECRET"])
To check your verifier, use Fourthwall's example secret key e3f93c7c-c92b-4b8f-a9b1-5b70e0891abc and this exact body:
{"testMode":true,"id":"weve_geAva6c1RAuyb9HQxbSlmA","webhookId":"wcon_P-VkRfmJTBaC6_Tst22cew","shopId":"sh_7ad0c438-beda-4779-a885-0dc325a755c1","type":"ORDER_PLACED","apiVersion":"V1","createdAt":"2023-07-12T15:05:11.078089+00:00","data":{"id":"00aa4abd-5778-4199-8161-0b49b2f212e5","friendlyId":"D3XZFWPP"}}
The header should be:
Wbbv1N1jsJGOsCmoyLQSngQMIHSexjph5P5vCPDAMjw=
Base64 is case-sensitive, so compare the strings exactly. A hex digest of the same HMAC will never match.
Make Fourthwall webhooks production-ready. Hookdeck Event Gateway verifies
X-Fourthwall-Hmac-SHA256with your shop's secret key, accepts each event as soon as it arrives, and durably queues it, so a slow handler doesn't use up Fourthwall's five attempts.
Fourthwall webhook limitations and pain points
A short response window
The Problem: Orders are processed, but Fourthwall still marks deliveries as failed and sends them again.
Why It Happens: Only an HTTP 200 counts as delivered, and Fourthwall warns that responses slower than 2000 ms may fail. A handler that writes to a database, calls a shipping partner and sends an email before replying can easily take longer. A 201 or 204 also counts as a failure.
Workarounds:
- Verify, store the event, and return exactly
200before doing any other work. - Move the processing to a background queue.
How Hookdeck Can Help: Hookdeck acknowledges Fourthwall's request itself and queues the event, then delivers it to your service with its own timeout and retries. Your handler can take as long as it needs.
Five attempts, and no promise of delivery
The Problem: After an outage, some orders or donations never reach your system.
Why It Happens: Fourthwall tries a failed event 5 times with a 5-second timeout each, and does not publish the gap between attempts. It also states that "webhook delivery is not always guaranteed".
Workarounds:
- Run a reconciliation job, as Fourthwall recommends, that fetches recent orders and memberships from the Platform API and fills any gaps.
- Use
GET /open-api/v1.0/webhook-eventsto list past events and find those whose state shows a failed delivery.
How Hookdeck Can Help: Hookdeck stores every event it receives, so an outage on your side no longer burns through Fourthwall's attempts. Failed deliveries to your service are retried on a schedule you set, can be replayed in bulk, and raise issues so you know something went wrong.
Duplicates and out-of-order topics
The Problem: A stream alert fires twice for one donation, or an order update arrives before the order itself.
Why It Happens: Delivery is at least once, and Fourthwall says "ordering is not guaranteed between different topics for the same resource", so ORDER_UPDATED can come before ORDER_PLACED.
Workarounds:
- Record each processed
idand skip any you have already seen. - When an update arrives for an order you don't know about, fetch the order from the API, or hold the update briefly, rather than failing.
How Hookdeck Can Help: Hookdeck's deduplication can drop repeated deliveries by id before they reach your handler.
Platform App webhooks use a different header
The Problem: A Platform App's webhooks fail verification even though the shop webhooks work.
Why It Happens: Platform Apps sign with the app's own HMAC key and send the signature in X-Fourthwall-Hmac-Apps-SHA256. A shop secret won't verify an app webhook, and Hookdeck's Fourthwall source type reads only the shop header.
Workarounds:
- Verify app webhooks in your receiver, using the same code with the Apps header and the app's HMAC key.
How Hookdeck Can Help: Use the Fourthwall source type for shop webhooks. For app webhooks, use a separate source with generic HMAC verification: SHA-256, base64 encoding, the header X-Fourthwall-Hmac-Apps-SHA256, and the app's HMAC key as the secret. That is the same check the Fourthwall source type runs, read from the Apps header.
Test events mixed in with real ones
The Problem: A test notification triggers a real fulfilment step or a stream alert.
Why It Happens: Send test notification sends test data to the same endpoint as live events. The only marker is testMode: true in the body.
Workarounds:
- Check
testModefirst and skip side effects for test events, while still returning200.
How Hookdeck Can Help: A filter on testMode can send test events to a separate destination, or stop them before they reach production.
Best practices
Verify against the raw body
The signature covers the exact bytes Fourthwall sent. Mount express.raw({ type: '*/*' }) in Express, use Buffer.from(await request.arrayBuffer()) in a Next.js route handler, or read await request.body() in FastAPI. Parse JSON only after the check.
Return exactly 200, quickly
Fourthwall treats any other status as a failure. Verify, store and respond, then process from a queue. See why to process webhooks asynchronously.
Deduplicate on id
Fourthwall tells you to treat repeated IDs as duplicates. Store processed IDs with a unique constraint. Our guide to webhook idempotency covers the patterns.
Route on type, and acknowledge the rest
Switch on type and return 200 for types you don't handle, so a newly added event never counts as a failure.
Reconcile on a schedule
Because delivery is not guaranteed, compare your records with the Platform API from time to time. This matters most for orders and memberships, where a missed event means a missed shipment or the wrong access level.
Conclusion
Fourthwall signs every shop webhook with a base64 HMAC-SHA256 of the raw body, keyed with the shop's secret key as plain text. Verify X-Fourthwall-Hmac-SHA256 against the exact bytes, return 200 within two seconds, deduplicate on id, cope with topics arriving out of order, and run a reconciliation job, because Fourthwall makes only five attempts and does not guarantee delivery.
Hookdeck Event Gateway verifies each signature, answers Fourthwall straight away, and holds every event until your service has processed it.
Get started with Hookdeck for free and handle Fourthwall webhooks reliably in minutes.