Gareth Wilson Gareth Wilson

Guide to Fourthwall Webhooks: Features and Best Practices

Published


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

FeatureDetails
ConfigurationDashboard (Settings > For developers) or the Platform API
Subscription modelOne endpoint per webhook, with a list of event types (allowedTypes)
DeliveryOne event per HTTPS POST, JSON body
Event typeThe type field in the body. No header carries it
Signature headerX-Fourthwall-Hmac-SHA256 (Platform Apps: X-Fourthwall-Hmac-Apps-SHA256)
SchemeHMAC-SHA256, base64
Signed contentThe entire raw body
SecretThe shop's secret key, a UUID-shaped string used as UTF-8 text
Replay protectionNone. Nothing time-based is signed
ResponseOnly 200 counts as delivered. Responses slower than 2000 ms may fail
Retries5 attempts with a 5-second timeout each. No interval is published
GuaranteesAt least once, unordered between topics, and not guaranteed
TestingSend test notification in the dashboard sends an event with testMode: true
Event logGET /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:

AreaEventFired when
OrdersORDER_PLACEDA new order is placed and paid
ORDER_UPDATEDAn order's status, shipping address or email changes
GiftsGIFT_PURCHASEA gift card is purchased
GIFT_DRAW_STARTED, GIFT_DRAW_ENDEDA giveaway draw starts or ends
SupportDONATIONA donation is received
THANK_YOU_SENTA thank you message is sent to a supporter
MembershipsSUBSCRIPTION_PURCHASEDA membership subscription is purchased
SUBSCRIPTION_CHANGEDA membership changes, for example a tier change
SUBSCRIPTION_EXPIREDA membership expires
CataloguePRODUCT_CREATED, PRODUCT_UPDATEDA product is created or updated
COLLECTION_UPDATEDA collection is updated
MarketingNEWSLETTER_SUBSCRIBEDSomeone subscribes to the newsletter
PROMOTION_CREATED, PROMOTION_UPDATEDA promotion is created or updated
PROMOTION_STATUS_CHANGEDA promotion's status changes, for example when it is activated or expires
CartsCART_ABANDONED_1H, CART_ABANDONED_24H, CART_ABANDONED_72HA cart has been inactive for 1, 24 or 72 hours. The 72-hour event is the final abandonment signal
AppsPLATFORM_APP_DISCONNECTEDA 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

  1. 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 as FOURTHWALL_WEBHOOK_SECRET, exactly as shown.
  2. Add a webhook with your HTTPS endpoint URL and select the event types it should receive.
  3. Click Send test notification on the webhook list. The event arrives with testMode set to true.

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-SHA256 with 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 200 before 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-events to 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 id and 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 testMode first and skip side effects for test events, while still returning 200.

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.


Gareth Wilson

Gareth Wilson

Product Marketing

Multi-time founding marketer, Gareth is PMM at Hookdeck and author of the newsletter, Community Inc.