# Claude App Source: https://docs.fourthwall.com/ai/claude-app Manage your Fourthwall shop in Claude with natural conversation Connect your Fourthwall shop to Claude so you can manage products, orders, promotions, and more through natural conversation — without leaving Claude. The Fourthwall Claude App is a connector for [Claude.ai](https://claude.ai) that gives Claude direct access to your shop. Once connected, you can ask Claude to do things like *"show me my last 10 orders,"* *"create a 20% off promo code,"* or *"design a new hoodie"* and Claude will handle the work for you using live data from your Fourthwall shop. ## What you can do with it Instead of clicking through your dashboard, you can just ask Claude. A few examples of the kinds of requests the Claude App understands: * **Products & inventory** — *"Hide the black hoodie from my store,"* *"How much stock do I have left on the tour tee?"*, *"Add the tour tee to my Fall 2026 collection."* * **Create new merch** — *"Create a new t-shirt with this design"* (Claude walks you through uploading artwork, picking products, and previewing the final result) * **Orders** — *"Show me today's orders,"* *"What's the status of order #1042?"*, *"List donations from the last week."* * **Promotions** — *"Create a SUMMER20 promo code for 20% off,"* *"Deactivate the BLACKFRIDAY discount."* Claude handles the heavy lifting: it picks the right tool, fills in the parameters, and shows you the results directly in the chat. For anything that changes your shop (creating a promo, updating a product, publishing new merch) Claude always confirms before making the change. Looking for analytics, financial reports, memberships data, integrations, or help-article search? Those live on the developer-facing MCP server at `https://mcp.fourthwall.com` — see the [MCP Server guide](/ai/mcp). ## Before you start You'll need: * A Fourthwall shop * A [Claude.ai](https://claude.ai) account (free tier works) ## Step 1: Open Claude's Connectors page Sign in to [claude.ai](https://claude.ai) and go to **Customize > Connectors**. You can also get there from your profile menu in the top-right corner. Claude.ai Customize > Connectors page showing the list of connectors ## Step 2: Add Fourthwall as a custom connector Click the **+** (Add connector) button at the top of the Connectors list, then choose **Add custom connector**. In the dialog, enter: * **Name:** `Fourthwall` * **Server URL:** `https://ai-apps.fourthwall.com/mcp` Add custom connector dialog with Name set to Fourthwall and Server URL set to https://ai-apps.fourthwall.com/mcp Then click **Add**. ## Step 3: Connect your shop Fourthwall will now appear in your connectors list. Click **Connect**. Fourthwall connector page showing 'You are not connected to Fourthwall yet' and a Connect button A Fourthwall sign-in window will open. Log in with the same account you use for your Fourthwall shop and authorize Claude to access it. If you run multiple shops on one account, you'll be prompted to pick which shop to use after you connect. Once you're back in Claude, Fourthwall will move from the *Not connected* section to the *Web* section at the top. That's it — you're connected. ## Step 4: Start a conversation Open a new chat in Claude and just ask for what you want. Claude will automatically use the Fourthwall connector when your message is about your shop. Try something like: > *"Create a 20% off promotion called SAVE20, limited to the first 100 customers."* Claude will call the Fourthwall connector, fill in any missing details using quick-pick widgets in the chat, and confirm before creating the promotion in your shop. ## Tips for getting the best results * **Review before you confirm.** Any action that changes your shop will show a preview in the chat first. Double-check the details before clicking confirm. * **Upload artwork for new products.** When you ask Claude to create merch, it will open an in-chat upload widget. You don't need to paste images into the chat — use the upload button the widget provides. * **Iterate on previews.** When creating merch, Claude renders previews against your artwork — keep asking for tweaks ("try it on a heavyweight hoodie", "show me a mockup in black") until you're ready to publish. ## Disconnecting or reconnecting To disconnect Fourthwall, go back to **Customize > Connectors**, click **Fourthwall**, and choose **Disconnect**. Your shop data is not deleted — you're just revoking Claude's access. If you ever see Claude say that the Fourthwall session expired, open the connector page and click **Connect** again to refresh the connection. ## Frequently asked questions Yes. The Fourthwall Claude App is free, and it works with the free tier of Claude.ai — you don't need a paid Claude plan to use it. The Fourthwall Claude App only sees what's needed to answer your request. Claude can only take actions on your behalf after you confirm them in the chat, and you can disconnect at any time from the Connectors page. Yes. Once you've connected Fourthwall on claude.ai, it works everywhere you're signed into Claude — web, desktop app, and mobile app. The Fourthwall MCP server at `https://ai-apps.fourthwall.com/mcp` is standards-based, so it can also be added to ChatGPT and other tools that support MCP connectors. This page covers Claude specifically. Go to **Customize > Connectors**, select **Fourthwall**, and click **Connect** again. This refreshes your login without removing any settings. If you have any questions, contact us at [support@fourthwall.com](mailto:support@fourthwall.com). # LLM Support Source: https://docs.fourthwall.com/ai/llm-support Use Fourthwall documentation with AI code editors Our documentation is optimized for use with Large Language Models (LLMs) and AI code editors. We provide specially formatted documentation files that help AI assistants generate accurate code and provide better guidance. ## Documentation Files We provide two versions of our documentation optimized for AI consumption: | File | Size | Description | | ---------------------------------------------------------- | ------- | ---------------------------------------------------- | | [llms.txt](https://docs.fourthwall.com/llms.txt) | \~45KB | Compact version with hierarchical sections and links | | [llms-full.txt](https://docs.fourthwall.com/llms-full.txt) | \~592KB | Complete documentation with full content | These files follow the [llms.txt](https://llmstxt.org/) standard. ## Quick Setup Add this to your AI editor's configuration file (`.cursorrules`, `CLAUDE.md`, `.github/copilot-instructions.md`, etc.): ```markdown theme={null} # Fourthwall Development Fourthwall is an e-commerce platform for creators to sell merchandise, memberships, and digital products. Documentation: https://docs.fourthwall.com/llms-full.txt When working with Fourthwall APIs: - Platform API: shop management (authentication, products, orders, inventory, webhooks) - Storefront API: custom storefronts (cart, checkout, collections) - Apps: OAuth apps with multi-shop support - Security: HMAC signature verification for webhooks and embedded sections ``` ## Context7 Integration Our documentation is also available via the **[Context7 MCP](https://context7.com/)** server. **Project ID:** `/llmstxt/fourthwall_llms-full_txt` For setup instructions for your editor (Claude, Cursor, VSCode, Windsurf, Zed, etc.), see the [Context7 Installation Guide](https://github.com/upstash/context7). ## What's Included * **Platform API** - Authentication, products, orders, inventory, webhooks * **Apps** - OAuth apps, app sections, app webhooks * **Storefront** - Custom storefronts, cart management, checkout * **Webhooks** - Event types, signature verification, testing * **Code Examples** - Authentication, API calls, signature verification ## Additional Resources * [Fourthwall Brand Guidelines](https://fourthwall.com/brand) * [Fourthwall Brand llms.txt](https://fourthwall.com/brand/llms.txt) For questions or issues with AI integrations, join our [Discord community](https://discord.com/invite/kc6P68z3Uz) in #developers-general. # MCP Server Source: https://docs.fourthwall.com/ai/mcp Connect AI assistants to manage your Fourthwall shop with the Model Context Protocol The Fourthwall MCP server gives AI assistants direct access to your shop — products, orders, promotions, collections, analytics, and more. Assistants can both **read** your shop data and **make changes** on your behalf. ## What You Can Do Once connected, you can ask your AI assistant things like: **Read & analyze** * *"Show me my top-selling products this month"* * *"List all unfulfilled orders from the past week"* * *"What's my conversion rate compared to last month?"* * *"Find all orders from [customer@example.com](mailto:customer@example.com)"* * *"What are my sales by country this quarter?"* * *"How many active members do I have?"* * *"Search help docs for how to set up shipping profiles"* **Manage & update** * *"Create a 20% off promotion for this weekend"* * *"Bump the price on all my t-shirt variants by \$2"* * *"Unpublish the sold-out hoodie"* * *"Refund order D3XZFWPP"* * *"Create a new collection and add my summer products to it"* * *"Issue 10 gift cards worth \$25 each"* ## Connect to the MCP Server **Server URL:** `https://mcp.fourthwall.com` (Streamable HTTP) 1. Open Claude settings and navigate to **Connectors** 2. Select **Add custom connector** 3. Enter name: `Fourthwall` 4. Enter URL: `https://mcp.fourthwall.com` 5. Select **Add** 6. You'll be prompted to log in with your Fourthwall account Run in your terminal: ```bash theme={null} claude mcp add --transport http fourthwall https://mcp.fourthwall.com ``` On first use, you'll be redirected to log in with your Fourthwall account. Run in your terminal: ```bash theme={null} codex mcp add fourthwall --url https://mcp.fourthwall.com ``` Then start the OAuth flow to log in with your Fourthwall account: ```bash theme={null} codex mcp login fourthwall ``` Or add the server to `~/.codex/config.toml` by hand and run `codex mcp login fourthwall`: ```toml theme={null} [mcp_servers.fourthwall] url = "https://mcp.fourthwall.com" ``` 1. Open command palette (`Cmd+Shift+P` / `Ctrl+Shift+P`) 2. Search for **Open MCP settings** 3. Add to your `mcp.json`: ```json theme={null} { "mcpServers": { "fourthwall": { "url": "https://mcp.fourthwall.com" } } } ``` 1. Open command palette (`Cmd+Shift+P` / `Ctrl+Shift+P`) 2. Search for **MCP: Add Server** 3. Select **HTTP** 4. Enter URL: `https://mcp.fourthwall.com` 5. Enter name: `fourthwall` Add to your `~/.gemini/settings.json`: ```json theme={null} { "mcpServers": { "fourthwall": { "httpUrl": "https://mcp.fourthwall.com" } } } ``` Any MCP-compatible client can connect using the Streamable HTTP transport: * **URL:** `https://mcp.fourthwall.com` * **Transport:** Streamable HTTP * **Authentication:** OAuth 2.0 (initiated automatically on first connection) ## Authentication The MCP server uses **OAuth 2.0** to authenticate with your Fourthwall account. When you connect for the first time: 1. Your AI client opens a browser window to Fourthwall's login page 2. You log in with your existing Fourthwall credentials 3. You select which shop to connect (if you manage multiple shops) 4. The session is established — you're ready to go ## Available Tools The MCP server exposes tools across the following categories. Most are **read** tools that retrieve data; the [Write & Management Tools](#write-management-tools) section below covers tools that change your shop. ### Search & Knowledge | Tool | Description | | ---------------- | --------------------------------------------------------------------------------- | | `omni_search` | Search across orders, products, promotions, and collections by name, email, or ID | | `omni_knowledge` | Search Fourthwall's help docs and knowledge base | ### Products & Catalog | Tool | Description | | ----------------------------------------- | ---------------------------------------------- | | `ecommerce_get-offers` | List all products in your shop | | `ecommerce_get-offers-by-ids` | Get specific products by ID | | `ecommerce_get-catalog-products` | Browse the Fourthwall product catalog | | `ecommerce_get-catalog-products-by-slugs` | Get catalog products by URL slugs | | `ecommerce_get-catalog-product-details` | Get details for a catalog product | | `ecommerce_validate-combined-listing` | Validate a combined listing before creating it | ### Orders & Fulfillment | Tool | Description | | ----------------------------------------- | ---------------------------------------------- | | `ecommerce_get-orders` | List orders with filters | | `ecommerce_get-orders-statistics` | Get order statistics and summaries | | `ecommerce_get-order-details-by-ids` | Get full details for specific orders | | `ecommerce_get-unfulfilled-orders` | List orders awaiting fulfillment | | `ecommerce_get-order-cancellation-by-ids` | Check cancellation status | | `ecommerce_get-fulfillment-details` | Get fulfillment tracking information | | `ecommerce_preview-cancel-cost` | Preview the cost impact of cancelling an order | | `ecommerce_preview-refund-cost` | Preview the cost impact of refunding an order | ### Collections | Tool | Description | | ---------------------------------- | ------------------------ | | `ecommerce_get-collections` | List all collections | | `ecommerce_get-collections-by-ids` | Get specific collections | ### Promotions | Tool | Description | | --------------------------------- | ----------------------- | | `ecommerce_get-promotions` | List all promotions | | `ecommerce_get-promotions-by-ids` | Get specific promotions | ### Gift Cards & Giveaways | Tool | Description | | ------------------------------ | ---------------------- | | `ecommerce_get-gift-card` | Get a single gift card | | `ecommerce_get-gift-cards` | List gift cards | | `ecommerce_get-giveaway-links` | List giveaway links | ### Analytics & Reports | Tool | Description | | ------------------------------------------------- | ---------------------------- | | `ecommerce_get-sales-over-time-report` | Revenue over a date range | | `ecommerce_get-top-products-by-units-sold-report` | Best-selling products | | `ecommerce_get-average-order-value-report` | Average order value trends | | `ecommerce_get-conversion-rates-report` | Storefront conversion rates | | `ecommerce_get-visitors-report` | Visitor traffic data | | `ecommerce_get-sales-by-country-report` | Revenue breakdown by country | | `ecommerce_get-sales-by-source-report` | Revenue by traffic source | | `ecommerce_get-sales-by-utm-report` | Revenue by UTM campaign | | `ecommerce_get-sessions-by-source-report` | Sessions by traffic source | | `ecommerce_get-customers-over-time-report` | Customer growth over time | | `ecommerce_get-total-profit-report` | Total profit overview | | `ecommerce_get-contributions-per-type-report` | Revenue by product type | | `ecommerce_get-payment-method-used-report` | Payment method breakdown | | `ecommerce_get-affiliate-earnings-report` | Affiliate earnings summary | ### Memberships | Tool | Description | | ---------------------------------------------------------- | ----------------------- | | `ecommerce_get-membership-active-members-report` | Active members overview | | `ecommerce_get-membership-new-members-report` | New member signups | | `ecommerce_get-membership-cancelled-members-report` | Cancellation data | | `ecommerce_get-membership-free-trials-report` | Free trial activity | | `ecommerce_get-membership-free-accounts-report` | Free account data | | `ecommerce_get-membership-tier-activity-report` | Activity by tier | | `ecommerce_get-membership-top-tiers-by-new-members-report` | Top-performing tiers | ### Integrations & Shipping | Tool | Description | | ------------------------------------ | ------------------------------ | | `ecommerce_get-all-integrations` | List connected integrations | | `ecommerce_get-tiktok-configuration` | TikTok integration settings | | `ecommerce_get-tiktok-products` | TikTok product sync status | | `ecommerce_get-youtube-integrations` | YouTube integration settings | | `ecommerce_get-youtube-products` | YouTube product sync status | | `ecommerce_get-shipping-profiles` | Shipping profile configuration | | `ecommerce_get-shipping-flat-rates` | Flat rate shipping settings | | `ecommerce_get-dns-entries` | Custom domain DNS records | | `ecommerce_validate-dns-entries` | Validate DNS configuration | ### Shop & Account | Tool | Description | | -------------------------------------------- | ------------------------------------ | | `ecommerce_get-current-shop` | Your shop details | | `ecommerce_get-current-subscription` | Your current Fourthwall plan | | `ecommerce_get-available-plans` | Available plan options | | `ecommerce_get-subscription-limits` | Plan limits and usage | | `ecommerce_get-shop-permissions` | Your account permissions | | `ecommerce_get-user-connections` | Shops connected to your account | | `ecommerce_get-payout-info` | Payout configuration | | `ecommerce_get-payout-transactions` | Payout transaction history | | `ecommerce_get-sample-credit-balance` | Sample credit balance | | `ecommerce_get-personalized-recommendations` | Personalized product recommendations | ## Write & Management Tools Beyond reading data, the MCP server can make changes to your shop. Write operations modify live data, so your assistant will typically confirm the details before acting, and your account permissions still apply — some actions require **Manager** or **Super Admin** access. ### Products | Tool | Description | | -------------------------------------------- | ---------------------------------- | | `ecommerce_create-digital-offer` | Create a new digital product | | `ecommerce_duplicate-offer` | Duplicate an existing product | | `ecommerce_update-offer` | Update a product's details | | `ecommerce_update-offer-slug` | Change a product's URL slug | | `ecommerce_update-offer-status` | Publish or unpublish a product | | `ecommerce_update-offer-variant` | Update a product variant | | `ecommerce_bulk-update-offer-variant-prices` | Bulk-update prices across variants | | `ecommerce_create-bundle` | Create a product bundle | | `ecommerce_update-bundle` | Update a product bundle | | `ecommerce_create-combined-listing` | Create a combined listing | | `ecommerce_update-combined-listing` | Update a combined listing | ### Collections | Tool | Description | | ---------------------------------------------- | ---------------------------------------------- | | `ecommerce_create-collection` | Create a new collection | | `ecommerce_update-collection-details` | Update a collection's name and description | | `ecommerce_update-collection-slug` | Change a collection's URL slug | | `ecommerce_update-collection-state` | Publish or unpublish a collection | | `ecommerce_update-collection-availability` | Set a collection's availability | | `ecommerce_update-collection-sorting-strategy` | Change how products are sorted in a collection | ### Promotions | Tool | Description | | --------------------------------------- | ---------------------------------------- | | `ecommerce_create-shop-promotion` | Create a shop-wide promotion or discount | | `ecommerce_create-membership-promotion` | Create a membership promotion | | `ecommerce_update-promotion` | Update an existing promotion | | `ecommerce_activate-promotion` | Activate a promotion | | `ecommerce_deactivate-promotion` | Deactivate a promotion | ### Orders & Fulfillment | Tool | Description | | ----------------------------------------- | ----------------------------------------------- | | `ecommerce_cancel-order` | Cancel an order | | `ecommerce_refund-order` | Refund an order | | `ecommerce_change-order-shipping-address` | Update an order's shipping address | | `ecommerce_edit-self-fulfilled-tracking` | Edit tracking details for self-fulfilled orders | ### Gift Cards & Giveaways | Tool | Description | | --------------------------------- | ---------------------------- | | `ecommerce_create-gift-cards` | Issue one or more gift cards | | `ecommerce_create-giveaway-links` | Create giveaway links | | `ecommerce_cancel-giveaway-links` | Cancel giveaway links | ### Samples & Shop | Tool | Description | | ----------------------------------- | ------------------------------------------ | | `ecommerce_create-sample-checkout` | Create a checkout to order product samples | | `ecommerce_update-shop-site-status` | Bring your shop's site online or offline | ### Product Design Create product designs from artwork and turn them into live products. These tools power the AI-assisted product creation flow. | Tool | Description | | -------------------------------------------- | --------------------------------------------- | | `ecommerce_generate-product-design-previews` | Generate design previews on a catalog product | | `ecommerce_rerender-design-previews` | Re-render design previews | | `ecommerce_create-offers-from-designs` | Create products from finished designs | | `ecommerce_create-offers-from-products` | Create products from catalog products | | `ecommerce_inspect-design` | Inspect a product design | | `ecommerce_edit-design` | Edit a product design | | `ecommerce_add-design-region` | Add a print region to a design | | `ecommerce_remove-design-region` | Remove a print region from a design | | `ecommerce_get-design-pipeline-status` | Check the status of a design generation job | | `ecommerce_get-draft-attributes` | Get a product draft's color and size options | | `ecommerce_add-draft-colors` | Add colors to a product draft | | `ecommerce_remove-draft-colors` | Remove colors from a product draft | | `ecommerce_add-draft-sizes` | Add sizes to a product draft | | `ecommerce_remove-draft-sizes` | Remove sizes from a product draft | | `ecommerce_apply-draft-to-product` | Apply draft changes to a product | | `ecommerce_get-customization-pricing` | Get pricing for product customization options | ### Media | Tool | Description | | ----------------------- | --------------------------------------------------- | | `upload-image-from-url` | Upload an image from a URL for use across your shop | ## Send Feedback The MCP server includes a built-in `send-feedback` tool that lets you report issues or request features directly from your AI conversation. You can ask your assistant things like: * *"Send feedback that the orders search is slow when filtering by date range"* * *"Submit a feature request for bulk order export"* Your feedback goes directly to the Fourthwall team along with relevant conversation context, so we can understand and address it quickly. ## Troubleshooting **I can't connect or authenticate** * Make sure you have a Fourthwall account with dashboard access * Try disconnecting and reconnecting the MCP server in your client * Check that your client supports the Streamable HTTP transport **Tools are missing or unavailable** * Make sure you've selected a shop after authenticating * Reconnect the MCP server if tools don't appear after login **I'm getting errors on tool calls** * Verify you have the correct permissions for the action (e.g., Super Admin or Manager for some operations) * Check that the entity IDs you're referencing exist in your shop * Use `send-feedback` to report persistent issues — include what you were trying to do ## Related Resources Documentation files and MCP server for AI code editors RESTful API for direct programmatic access API key and OAuth authentication guides Join our Discord for help and discussions # Cart abandoned 1 hour Source: https://docs.fourthwall.com/api-reference/cart-events/cart-abandoned-1-hour /openapi/platform.json webhook CART_ABANDONED_1H Fired when a cart has been inactive for 1 hour. # Cart abandoned 24 hours Source: https://docs.fourthwall.com/api-reference/cart-events/cart-abandoned-24-hours /openapi/platform.json webhook CART_ABANDONED_24H Fired when a cart has been inactive for 24 hours. # Cart abandoned 72 hours Source: https://docs.fourthwall.com/api-reference/cart-events/cart-abandoned-72-hours /openapi/platform.json webhook CART_ABANDONED_72H Fired when a cart has been inactive for 72 hours. This is the final abandonment signal. # Get current channel for channel API client Source: https://docs.fourthwall.com/api-reference/channel-api/channel/get-current-channel openapi/channel-api.json GET /channel-api/v1.0/channel/current This endpoint is in beta and subject to change. # Register an uploaded image into the shop's media library Source: https://docs.fourthwall.com/api-reference/channel-api/media-library/register-media-image openapi/channel-api.json POST /channel-api/v1.0/media/images Returns a media image id to reference as a region's `imageId` in POST /previews. This endpoint is in beta and subject to change. # Create a preview pipeline for a product Source: https://docs.fourthwall.com/api-reference/channel-api/previews/create-preview openapi/channel-api.json POST /channel-api/v1.0/previews This endpoint is in beta and subject to change. # Create a shop with products Source: https://docs.fourthwall.com/api-reference/channel-api/shops/create-shop openapi/channel-api.json POST /channel-api/v1.0/shops This endpoint is in beta and subject to change. # List shops attached to the agency Source: https://docs.fourthwall.com/api-reference/channel-api/shops/get-shops openapi/channel-api.json GET /channel-api/v1.0/shops This endpoint is in beta and subject to change. # Request a presigned upload URL for artwork Source: https://docs.fourthwall.com/api-reference/channel-api/shops/request-upload-url openapi/channel-api.json POST /channel-api/v1.0/upload-url This endpoint is in beta and subject to change. **When you `PUT` the bytes to `uploadUrl`, you must send two headers that are baked into the URL's signature** — omit or mismatch either and Google Cloud Storage rejects the upload with `403 SignatureDoesNotMatch`: * `Content-Type` — must equal the `contentType` you sent in this request. * `x-goog-content-length-range: 0,` — must use the same `size` (in bytes) you sent in this request. The `PUT` goes **directly to Google Cloud Storage**, not Fourthwall — don't send your Fourthwall credentials with it, and note the URL is short-lived. # Collection updated Source: https://docs.fourthwall.com/api-reference/collection-events/collection-updated /openapi/platform.json webhook COLLECTION_UPDATED Fired when a collection is updated. # Gift draw ended Source: https://docs.fourthwall.com/api-reference/giveaway-events/gift-draw-ended /openapi/platform.json webhook GIFT_DRAW_ENDED Fired when a giveaway draw ends. # Gift draw started Source: https://docs.fourthwall.com/api-reference/giveaway-events/gift-draw-started /openapi/platform.json webhook GIFT_DRAW_STARTED Fired when a giveaway draw is started. # Subscription changed Source: https://docs.fourthwall.com/api-reference/membership-events/subscription-changed /openapi/platform.json webhook SUBSCRIPTION_CHANGED Fired when a membership subscription is changed (e.g., tier change). # Subscription expired Source: https://docs.fourthwall.com/api-reference/membership-events/subscription-expired /openapi/platform.json webhook SUBSCRIPTION_EXPIRED Fired when a membership subscription expires. # Subscription purchased Source: https://docs.fourthwall.com/api-reference/membership-events/subscription-purchased /openapi/platform.json webhook SUBSCRIPTION_PURCHASED Fired when a new membership subscription is purchased. # Working with Images Source: https://docs.fourthwall.com/api-reference/membership/getting-started-images Upload an image and reference it as a resource on other Memberships endpoints Many Memberships endpoints reference images by an **image resource ID** rather than accepting a raw file. For example, [Create Tier](/api-reference/membership/tiers/create-tier) takes an `imageResourceId`, [Create Video Post](/api-reference/membership/posts/create-video-post) takes an `imageResourceId`, and [Create Post Series](/api-reference/membership/series/create-post-series) takes a `coverImageResourceId` and a `thumbnailResourceId`. Before you can set one of those fields, you must upload the image and obtain its `resourceId`. Uploading is a three-step flow: **create the image**, **upload the bytes directly to storage**, then **confirm the upload**. All image endpoints require the `memberships_write` OAuth scope. API keys have full access. ## Overview ```mermaid theme={null} sequenceDiagram participant Client participant Fourthwall participant Storage as Cloud Storage Client->>Fourthwall: ① POST /memberships/images
{ byteSize, checksum } Fourthwall->>Client: ② { uploadData: { url, headers, signedId }, resourceId } Client->>Storage: ③ PUT bytes to uploadData.url
(with uploadData.headers) Client->>Fourthwall: ④ PATCH /memberships/images/{resourceId}/confirm
{ signedId } Fourthwall->>Client: ⑤ 204 No Content Note over Client: Use resourceId as imageResourceId,
thumbnailResourceId, etc. ``` 1. Ask Fourthwall to create an image, sending the file's `byteSize` and `checksum`. 2. Fourthwall responds with a signed, pre-authorized `uploadData.url`, the `headers` you must send with the upload, a `signedId`, and the `resourceId` you'll ultimately reference. 3. Upload the raw image bytes **directly to cloud storage** using the returned URL and headers. 4. Confirm the upload with Fourthwall, passing back the `signedId`. 5. Once confirmed, the `resourceId` is ready to be used on any endpoint that accepts an image resource. ## Step 1 — Create the image Compute the image's byte size and a base64-encoded **MD5** checksum, then call [Create Image](/api-reference/membership/resources/create-image). ```bash cURL theme={null} curl -X POST "https://api.fourthwall.com/open-api/v1.1/memberships/images" \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "byteSize": 20481, "checksum": "7vJP+O8buKShZwMbdl7wJQ==" }' ``` ```javascript JavaScript theme={null} import { readFile } from "node:fs/promises"; import { createHash } from "node:crypto"; const bytes = await readFile("./tier-image.png"); const checksum = createHash("md5").update(bytes).digest("base64"); const res = await fetch( "https://api.fourthwall.com/open-api/v1.1/memberships/images", { method: "POST", headers: { Authorization: `Bearer ${accessToken}`, "Content-Type": "application/json", }, body: JSON.stringify({ byteSize: bytes.length, checksum }), }, ); const { uploadData, resourceId } = await res.json(); ``` The response contains everything you need for the next two steps: ```json theme={null} { "uploadData": { "url": "https://storage.googleapis.com/...", "headers": { "content-MD5": "7vJP+O8buKShZwMbdl7wJQ==", "content-Disposition": "inline; filename=\"7c8f5486-7841-4890-841f-53a3ebe2b445\"; filename*=UTF-8''7c8f5486-7841-4890-841f-53a3ebe2b445", "cache-Control": "public, max-age=2592000" }, "signedId": "eyJfcmFpbHMiOnsib..." }, "resourceId": 123456 } ``` | Field | Description | | --------------------- | ------------------------------------------------------------------- | | `uploadData.url` | Pre-authorized URL to `PUT` the raw image bytes to. | | `uploadData.headers` | Headers you **must** send verbatim with the direct upload. | | `uploadData.signedId` | Opaque token identifying this upload. Pass it back in Step 3. | | `resourceId` | The ID you'll use as `imageResourceId`, `thumbnailResourceId`, etc. | The `checksum` you send here must be the base64-encoded MD5 of the exact bytes you upload in Step 2. Storage rejects the upload if the `content-MD5` header does not match the uploaded content. ## Step 2 — Upload the bytes directly `PUT` the raw image bytes to `uploadData.url`, sending every header from `uploadData.headers`. This request goes to cloud storage directly — **do not** send your Fourthwall `Authorization` header to it. ```bash cURL theme={null} curl -X PUT "$UPLOAD_URL" \ -H "Content-MD5: 7vJP+O8buKShZwMbdl7wJQ==" \ -H "Content-Disposition: inline; filename=\"...\"" \ -H "Cache-Control: public, max-age=2592000" \ --data-binary "@tier-image.png" ``` ```javascript JavaScript theme={null} await fetch(uploadData.url, { method: "PUT", headers: uploadData.headers, body: bytes, }); ``` A successful upload returns a `200 OK` from storage with no body. ## Step 3 — Confirm the upload Tell Fourthwall the bytes are in place by calling [Confirm Image Upload](/api-reference/membership/resources/confirm-post) with the `resourceId` in the path and the `signedId` from Step 1 in the body. ```bash cURL theme={null} curl -X PATCH "https://api.fourthwall.com/open-api/v1.1/memberships/images/123456/confirm" \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "signedId": "eyJfcmFpbHMiOnsib..." }' ``` ```javascript JavaScript theme={null} await fetch( `https://api.fourthwall.com/open-api/v1.1/memberships/images/${resourceId}/confirm`, { method: "PATCH", headers: { Authorization: `Bearer ${accessToken}`, "Content-Type": "application/json", }, body: JSON.stringify({ signedId: uploadData.signedId }), }, ); ``` A successful confirmation returns `204 No Content`. The image `resourceId` is now ready to use. ## Step 4 — Reference the image Pass the `resourceId` into whichever field the target endpoint expects. For example, when creating a paid tier: ```bash cURL theme={null} curl -X POST "https://api.fourthwall.com/open-api/v1.1/memberships/tiers" \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "Gold", "imageResourceId": 123456 }' ``` The same `resourceId` can be used for any image field across the Memberships API: | Field | Used by | | ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `imageResourceId` | [Create Tier](/api-reference/membership/tiers/create-tier), [Create Audio Post](/api-reference/membership/posts/create-audio-post), [Create Video Post](/api-reference/membership/posts/create-video-post) | | `thumbnailResourceId` | [Create Post Tag](/api-reference/membership/tags/create-post-tag), [Create Post Series](/api-reference/membership/series/create-post-series) | | `coverImageResourceId` | [Create Post Series](/api-reference/membership/series/create-post-series) | Use `https://api.fourthwall.com` for production. For testing against staging, swap in `https://api.staging.fourthwall.com`. # Get Member Source: https://docs.fourthwall.com/api-reference/membership/members/get-member openapi/membership.json GET /open-api/v1.1/memberships/members/{id} **OAuth scope:** `memberships_read` API keys have full access to this endpoint. # List Members Source: https://docs.fourthwall.com/api-reference/membership/members/list-members openapi/membership.json GET /open-api/v1.1/memberships/members **OAuth scope:** `memberships_read` API keys have full access to this endpoint. # Create Audio Post Source: https://docs.fourthwall.com/api-reference/membership/posts/create-audio-post openapi/membership.json POST /open-api/v1.1/memberships/posts/audios **OAuth scope:** `memberships_write` API keys have full access to this endpoint. # Create Image Post Source: https://docs.fourthwall.com/api-reference/membership/posts/create-image-post openapi/membership.json POST /open-api/v1.1/memberships/posts/images **OAuth scope:** `memberships_write` API keys have full access to this endpoint. # Create Poll Post Source: https://docs.fourthwall.com/api-reference/membership/posts/create-poll-post openapi/membership.json POST /open-api/v1.1/memberships/posts/polls **OAuth scope:** `memberships_write` API keys have full access to this endpoint. # Create Text Post Source: https://docs.fourthwall.com/api-reference/membership/posts/create-text-post openapi/membership.json POST /open-api/v1.1/memberships/posts/texts **OAuth scope:** `memberships_write` API keys have full access to this endpoint. # Create Video Embed Post Source: https://docs.fourthwall.com/api-reference/membership/posts/create-video-embed-post openapi/membership.json POST /open-api/v1.1/memberships/posts/video_embeds **OAuth scope:** `memberships_write` API keys have full access to this endpoint. # Create Video Post Source: https://docs.fourthwall.com/api-reference/membership/posts/create-video-post openapi/membership.json POST /open-api/v1.1/memberships/posts/videos **OAuth scope:** `memberships_write` API keys have full access to this endpoint. # Delete Post Source: https://docs.fourthwall.com/api-reference/membership/posts/delete-post openapi/membership.json DELETE /open-api/v1.1/memberships/posts/{id} **OAuth scope:** `memberships_write` API keys have full access to this endpoint. # Get Post Source: https://docs.fourthwall.com/api-reference/membership/posts/get-post openapi/membership.json GET /open-api/v1.1/memberships/posts/{id} **OAuth scope:** `memberships_read` API keys have full access to this endpoint. # List Posts Source: https://docs.fourthwall.com/api-reference/membership/posts/list-posts openapi/membership.json GET /open-api/v1.1/memberships/posts **OAuth scope:** `memberships_read` API keys have full access to this endpoint. # Pin Post Source: https://docs.fourthwall.com/api-reference/membership/posts/pin-post openapi/membership.json POST /open-api/v1.1/memberships/posts/{post_id}/pin **OAuth scope:** `memberships_write` API keys have full access to this endpoint. # Unpin Post Source: https://docs.fourthwall.com/api-reference/membership/posts/unpin-post openapi/membership.json DELETE /open-api/v1.1/memberships/posts/{post_id}/pin **OAuth scope:** `memberships_write` API keys have full access to this endpoint. # Update Image Post Source: https://docs.fourthwall.com/api-reference/membership/posts/update-image-post openapi/membership.json PUT /open-api/v1.1/memberships/posts/images/{id} **OAuth scope:** `memberships_write` API keys have full access to this endpoint. # Update Poll Post Source: https://docs.fourthwall.com/api-reference/membership/posts/update-poll-post openapi/membership.json PUT /open-api/v1.1/memberships/posts/polls/{id} **OAuth scope:** `memberships_write` API keys have full access to this endpoint. # Update Text Post Source: https://docs.fourthwall.com/api-reference/membership/posts/update-text-post openapi/membership.json PUT /open-api/v1.1/memberships/posts/texts/{id} **OAuth scope:** `memberships_write` API keys have full access to this endpoint. # Update Video Embed Post Source: https://docs.fourthwall.com/api-reference/membership/posts/update-video-embed-post openapi/membership.json PUT /open-api/v1.1/memberships/posts/video_embeds/{id} **OAuth scope:** `memberships_write` API keys have full access to this endpoint. # Confirm Image Upload Source: https://docs.fourthwall.com/api-reference/membership/resources/confirm-post openapi/membership.json PATCH /open-api/v1.1/memberships/images/{id}/confirm **OAuth scope:** `memberships_write` API keys have full access to this endpoint. # Create Image Source: https://docs.fourthwall.com/api-reference/membership/resources/create-image openapi/membership.json POST /open-api/v1.1/memberships/images **OAuth scope:** `memberships_write` API keys have full access to this endpoint. # Create Post Series Source: https://docs.fourthwall.com/api-reference/membership/series/create-post-series openapi/membership.json POST /open-api/v1.1/memberships/series/posts **OAuth scope:** `memberships_write` API keys have full access to this endpoint. # Delete Post Series Source: https://docs.fourthwall.com/api-reference/membership/series/delete-post-series openapi/membership.json DELETE /open-api/v1.1/memberships/series/posts/{id} **OAuth scope:** `memberships_write` API keys have full access to this endpoint. # Get Post Series Source: https://docs.fourthwall.com/api-reference/membership/series/get-post-series openapi/membership.json GET /open-api/v1.1/memberships/series/posts/{id} **OAuth scope:** `memberships_read` API keys have full access to this endpoint. # List Post Series Source: https://docs.fourthwall.com/api-reference/membership/series/list-post-series openapi/membership.json GET /open-api/v1.1/memberships/series/posts **OAuth scope:** `memberships_read` API keys have full access to this endpoint. # Update Post Series Source: https://docs.fourthwall.com/api-reference/membership/series/update-post-series openapi/membership.json PUT /open-api/v1.1/memberships/series/posts/{id} **OAuth scope:** `memberships_write` API keys have full access to this endpoint. # Create Post Tag Source: https://docs.fourthwall.com/api-reference/membership/tags/create-post-tag openapi/membership.json POST /open-api/v1.1/memberships/tags/posts **OAuth scope:** `memberships_write` API keys have full access to this endpoint. # Delete Post Tag Source: https://docs.fourthwall.com/api-reference/membership/tags/delete-post-tag openapi/membership.json DELETE /open-api/v1.1/memberships/tags/posts/{id} **OAuth scope:** `memberships_write` API keys have full access to this endpoint. # Get Post Tag Source: https://docs.fourthwall.com/api-reference/membership/tags/get-post-tag openapi/membership.json GET /open-api/v1.1/memberships/tags/posts/{id} **OAuth scope:** `memberships_read` API keys have full access to this endpoint. # List Post Tags Source: https://docs.fourthwall.com/api-reference/membership/tags/list-post-tags openapi/membership.json GET /open-api/v1.1/memberships/tags/posts **OAuth scope:** `memberships_read` API keys have full access to this endpoint. # Update Post Tag Source: https://docs.fourthwall.com/api-reference/membership/tags/update-post-tag openapi/membership.json PATCH /open-api/v1.1/memberships/tags/posts/{id} **OAuth scope:** `memberships_write` API keys have full access to this endpoint. # Create Tier Source: https://docs.fourthwall.com/api-reference/membership/tiers/create-tier openapi/membership.json POST /open-api/v1.1/memberships/tiers **OAuth scope:** `memberships_write` API keys have full access to this endpoint. # Get Tier Source: https://docs.fourthwall.com/api-reference/membership/tiers/get-tier openapi/membership.json GET /open-api/v1.1/memberships/tiers/{id} **OAuth scope:** `memberships_read` API keys have full access to this endpoint. # List Tiers Source: https://docs.fourthwall.com/api-reference/membership/tiers/list-tiers openapi/membership.json GET /open-api/v1.1/memberships/tiers **OAuth scope:** `memberships_read` API keys have full access to this endpoint. # Update Tier Source: https://docs.fourthwall.com/api-reference/membership/tiers/update-tier openapi/membership.json PATCH /open-api/v1.1/memberships/tiers/{id} **OAuth scope:** `memberships_write` API keys have full access to this endpoint. # Newsletter subscribed Source: https://docs.fourthwall.com/api-reference/newsletter-events/newsletter-subscribed /openapi/platform.json webhook NEWSLETTER_SUBSCRIBED Fired when someone subscribes to the newsletter. # Donation Source: https://docs.fourthwall.com/api-reference/order-events/donation /openapi/platform.json webhook DONATION Fired when a new donation is received. # Gift purchase Source: https://docs.fourthwall.com/api-reference/order-events/gift-purchase /openapi/platform.json webhook GIFT_PURCHASE Fired when a gift card is purchased. # Order placed Source: https://docs.fourthwall.com/api-reference/order-events/order-placed /openapi/platform.json webhook ORDER_PLACED Fired when a new order is successfully placed and paid. # Order updated Source: https://docs.fourthwall.com/api-reference/order-events/order-updated /openapi/platform.json webhook ORDER_UPDATED Fired when an order's status, shipping address, or email is updated. # Platform app disconnected Source: https://docs.fourthwall.com/api-reference/platform-events/platform-app-disconnected /openapi/platform.json webhook PLATFORM_APP_DISCONNECTED Fired when a platform app is disconnected from a shop. # List Collections Source: https://docs.fourthwall.com/api-reference/platform/collections/list-collections GET /open-api/v1.0/collections *Rate limit: 100 requests / 10 seconds per shop. See [Rate limiting](/guides/rate-limiting).* Returns all collections with pagination **OAuth scope:** `offer_read` API keys have full access to this endpoint. # Create a new customization Source: https://docs.fourthwall.com/api-reference/platform/customization/create-customization POST /open-api/v1.0/customizations *Rate limit: 5 requests / minute per shop. See [Rate limiting](/guides/rate-limiting).* Creates a shop-bound customization sketch on a product template. When `regions` are supplied, the sync design pipeline runs immediately and the response carries the rendered preview images. With no regions, only the sketch is created (`pipelineId: null`, `images: []`). Iteration = re-POST with adjusted selections; there is no update-in-place. **OAuth scope:** `offer_write` API keys have full access to this endpoint. # Get Donation Source: https://docs.fourthwall.com/api-reference/platform/donations/get-donation GET /open-api/v1.0/donations/{donationId} *Rate limit: 100 requests / 10 seconds per shop. See [Rate limiting](/guides/rate-limiting).* Returns donation by id **OAuth scope:** `donation_read` API keys have full access to this endpoint. # List Donations Source: https://docs.fourthwall.com/api-reference/platform/donations/list-donations GET /open-api/v1.0/donations *Rate limit: 100 requests / 10 seconds per shop. See [Rate limiting](/guides/rate-limiting).* Returns all donations with pagination **OAuth scope:** `donation_read` API keys have full access to this endpoint. # Create Fulfillment Source: https://docs.fourthwall.com/api-reference/platform/fulfillment/create-fulfillment POST /open-api/v1.0/fulfillments *Rate limit: 100 requests / 10 seconds per shop. See [Rate limiting](/guides/rate-limiting).* Creates a fulfillment with a shipment tracker for provided order items. When trackers change their state, order.status will change to IN_PRODUCTION, PARTIALLY_IN_PRODUCTION, PARTIALLY_SHIPPED, SHIPPED depending on the shipping tracker info. Order updated webhooks will be triggered. **OAuth scope:** `fulfillment_write` API keys have full access to this endpoint. # Get Gift Purchase Source: https://docs.fourthwall.com/api-reference/platform/gift-purchases/get-gift-purchase GET /open-api/v1.0/gift-purchase/{giftPurchaseId} *Rate limit: 100 requests / 10 seconds per shop. See [Rate limiting](/guides/rate-limiting).* Returns gift purchase details by id # Finish Draw Source: https://docs.fourthwall.com/api-reference/platform/gifting/finish-draw PUT /open-api/v1.0/gifting/draw/{id}/finish *Rate limit: 100 requests / 10 seconds per shop. See [Rate limiting](/guides/rate-limiting).* Finish draw and select winners **OAuth scope:** `giveaway_write` API keys have full access to this endpoint. # Get Draw Source: https://docs.fourthwall.com/api-reference/platform/gifting/get-draw GET /open-api/v1.0/gifting/draw/{id} *Rate limit: 100 requests / 10 seconds per shop. See [Rate limiting](/guides/rate-limiting).* Get draw details **OAuth scope:** `giveaway_read` API keys have full access to this endpoint. # Create Giveaway Links Source: https://docs.fourthwall.com/api-reference/platform/giveaway-links/create-giveaway-links POST /open-api/v1.0/giveaway-links *Rate limit: 100 requests / 10 seconds per shop. See [Rate limiting](/guides/rate-limiting).* Creates a new package with specified number of giveaway links **OAuth scope:** `giveaway_write` API keys have full access to this endpoint. # Get Giveaway Package Source: https://docs.fourthwall.com/api-reference/platform/giveaway-links/get-package GET /open-api/v1.0/giveaway-links/packages/{packageId} *Rate limit: 100 requests / 10 seconds per shop. See [Rate limiting](/guides/rate-limiting).* Returns all giveaway links for packageId **OAuth scope:** `giveaway_read` API keys have full access to this endpoint. # List Giveaway Packages Source: https://docs.fourthwall.com/api-reference/platform/giveaway-links/list-packages GET /open-api/v1.0/giveaway-links/packages *Rate limit: 100 requests / 10 seconds per shop. See [Rate limiting](/guides/rate-limiting).* Returns all packages with giveaway links **OAuth scope:** `giveaway_read` API keys have full access to this endpoint. # List Mailing List Entries Source: https://docs.fourthwall.com/api-reference/platform/mailing-lists/list-entries GET /open-api/v1.0/mailing-list-entries *Rate limit: 100 requests / 10 seconds per shop. See [Rate limiting](/guides/rate-limiting).* Returns all mailing list entries **OAuth scope:** `shop_read` API keys have full access to this endpoint. # List media library images Source: https://docs.fourthwall.com/api-reference/platform/media-library/list-media-images GET /open-api/v1.0/media/images *Rate limit: 100 requests / 10 seconds per shop. See [Rate limiting](/guides/rate-limiting).* Retrieves all images from the shop's media library **OAuth scope:** `media_read` API keys have full access to this endpoint. # Request a pre-signed upload URL Source: https://docs.fourthwall.com/api-reference/platform/media-library/request-media-upload-url POST /open-api/v1.0/media/upload-url *Rate limit: 20 requests / minute per shop. See [Rate limiting](/guides/rate-limiting).* Returns a pre-signed upload URL for uploading a new image. After receiving the response, PUT the image bytes directly to the uploadUrl. **OAuth scope:** `media_write` API keys have full access to this endpoint. **When you `PUT` the bytes to `uploadUrl`, you must send two headers that are baked into the URL's signature** — omit or mismatch either and Google Cloud Storage rejects the upload with `403 SignatureDoesNotMatch`: * `Content-Type` — must equal the `contentType` you sent in this request. * `x-goog-content-length-range: 0,` — must use the same `size` (in bytes) you sent in this request. The `PUT` goes **directly to Google Cloud Storage**, not Fourthwall — don't send your Fourthwall credentials with it, and note the URL is short-lived (\~6 hours). See [Create design products](/guides/create-design-products) for an end-to-end walkthrough. # Save an uploaded image to the media library Source: https://docs.fourthwall.com/api-reference/platform/media-library/save-media-image POST /open-api/v1.0/media/images *Rate limit: 100 requests / 10 seconds per shop. See [Rate limiting](/guides/rate-limiting).* Persists an uploaded image in the media library after the client has PUT it to the signed URL **OAuth scope:** `media_write` API keys have full access to this endpoint. # Get Member Source: https://docs.fourthwall.com/api-reference/platform/memberships/get-member GET /open-api/v1.0/memberships/members/{id} *Rate limit: 100 requests / 10 seconds per shop. See [Rate limiting](/guides/rate-limiting).* Gets a member by id **OAuth scope:** `memberships_read` API keys have full access to this endpoint. # List Members Source: https://docs.fourthwall.com/api-reference/platform/memberships/list-members GET /open-api/v1.0/memberships/members *Rate limit: 100 requests / 10 seconds per shop. See [Rate limiting](/guides/rate-limiting).* Lists all members for the current shop **OAuth scope:** `memberships_read` API keys have full access to this endpoint. # List Membership Tiers Source: https://docs.fourthwall.com/api-reference/platform/memberships/list-tiers GET /open-api/v1.0/memberships/tiers *Rate limit: 100 requests / 10 seconds per shop. See [Rate limiting](/guides/rate-limiting).* Lists all tiers for the current shop **OAuth scope:** `memberships_read` API keys have full access to this endpoint. # Get Order Source: https://docs.fourthwall.com/api-reference/platform/orders/get-order GET /open-api/v1.0/order/{orderId} *Rate limit: 100 requests / 10 seconds per shop. See [Rate limiting](/guides/rate-limiting).* Returns order by id **OAuth scope:** `order_read` API keys have full access to this endpoint. # Get Order by Friendly ID Source: https://docs.fourthwall.com/api-reference/platform/orders/get-order-by-friendly-id GET /open-api/v1.0/order/by-friendly-id/{friendlyId} *Rate limit: 100 requests / 10 seconds per shop. See [Rate limiting](/guides/rate-limiting).* Returns order by friendly id **OAuth scope:** `order_read` API keys have full access to this endpoint. # List Orders Source: https://docs.fourthwall.com/api-reference/platform/orders/list-orders GET /open-api/v1.0/order *Rate limit: 100 requests / 10 seconds per shop. See [Rate limiting](/guides/rate-limiting).* Returns all orders with pagination **OAuth scope:** `order_read` API keys have full access to this endpoint. # Mark Digital Download Complete Source: https://docs.fourthwall.com/api-reference/platform/orders/mark-download-complete PUT /open-api/v1.0/order/{orderId}/downloaded *Rate limit: 100 requests / 10 seconds per shop. See [Rate limiting](/guides/rate-limiting).* Marks digital download as downloaded. If no downloads exist for a digital order and defaultFileUrl is provided in the request body, creates a download with that URL and marks it as downloaded. **OAuth scope:** `order_write` API keys have full access to this endpoint. # Exchange Token Source: https://docs.fourthwall.com/api-reference/platform/platform-token/exchange-token POST /open-api/v1.0/platform/token *Rate limit: 100 requests / 10 seconds per shop. See [Rate limiting](/guides/rate-limiting).* OAuth 2.0 token endpoint. Supports 'authorization_code' grant type for exchanging authorization codes for tokens, and 'refresh_token' grant type for refreshing access tokens. # Get product template details Source: https://docs.fourthwall.com/api-reference/platform/product-templates/get-product-template GET /open-api/v1.0/product-templates/{productId} *Rate limit: 100 requests / 10 seconds per shop. See [Rate limiting](/guides/rate-limiting).* Get detailed information about a specific product template. This endpoint is public and does not require authentication. Returns full product details including variants, customizable areas, size guide, and images. Product image URLs (`colorVariants[].photos[].url`) are generated on demand by imgproxy and are **not durable** — they may change or expire. Fetch them fresh from this endpoint when you need to display images rather than storing or hardcoding them. # List product templates (specific page) Source: https://docs.fourthwall.com/api-reference/platform/product-templates/list-product-templates-paged GET /open-api/v1.0/product-templates/page/{page} *Rate limit: 100 requests / 10 seconds per shop. See [Rate limiting](/guides/rate-limiting).* List available product templates with pagination. This endpoint is public and does not require authentication. The `thumbnail` URL is generated on demand by imgproxy and is **not durable** — it may change or expire. Fetch it fresh from this endpoint when you need to display the image rather than storing or hardcoding it. # Archive a product (offer) Source: https://docs.fourthwall.com/api-reference/platform/products-(offers)/archive-product DELETE /open-api/v1.0/products/{productId} *Rate limit: 100 requests / 10 seconds per shop. See [Rate limiting](/guides/rate-limiting).* Soft-archives the product — sets it to `Archived`. Terminal at this surface: once archived, the product cannot be returned to `PUBLIC`/`HIDDEN` through the open-api (restoration stays admin-only). Idempotent: re-DELETE on an already-archived product also returns 204. **OAuth scope:** `offer_write` API keys have full access to this endpoint. # Update product (offer) lifecycle state Source: https://docs.fourthwall.com/api-reference/platform/products-(offers)/update-product-state PUT /open-api/v1.0/products/{productId}/state *Rate limit: 100 requests / 10 seconds per shop. See [Rate limiting](/guides/rate-limiting).* Transitions the product between `PUBLIC` and `HIDDEN`. Use `DELETE /products/{productId}` to archive — `ARCHIVED` is not reachable here. The sold-out (`available`) flag is preserved; flip it via `PUT /products/{productId}/availability`. Idempotent: no-op if the product is already in the requested state. **OAuth scope:** `offer_write` API keys have full access to this endpoint. # Create a product Source: https://docs.fourthwall.com/api-reference/platform/products/create-product POST /open-api/v1.0/products *Rate limit: 5 requests / minute per shop. See [Rate limiting](/guides/rate-limiting).* Creates a product from a design or a digital product. **OAuth scope:** `offer_write` API keys have full access to this endpoint. # Get Product Source: https://docs.fourthwall.com/api-reference/platform/products/get-product GET /open-api/v1.0/products/{productId} *Rate limit: 100 requests / 10 seconds per shop. See [Rate limiting](/guides/rate-limiting).* Returns product by id **OAuth scope:** `offer_read` API keys have full access to this endpoint. # Get Product Inventory Source: https://docs.fourthwall.com/api-reference/platform/products/get-product-inventory GET /open-api/v1.0/products/{productId}/inventory *Rate limit: 100 requests / 10 seconds per shop. See [Rate limiting](/guides/rate-limiting).* Returns product (offer) inventory by id **OAuth scope:** `offer_read` API keys have full access to this endpoint. # List Products Source: https://docs.fourthwall.com/api-reference/platform/products/list-products GET /open-api/v1.0/products *Rate limit: 100 requests / 10 seconds per shop. See [Rate limiting](/guides/rate-limiting).* Returns all products with pagination **OAuth scope:** `offer_read` API keys have full access to this endpoint. # Update Product Availability Source: https://docs.fourthwall.com/api-reference/platform/products/update-product-availability PUT /open-api/v1.0/products/{productId}/availability Toggle whether a product is visible and available for purchase on your storefront **OAuth scope:** `offer_write` API keys have full access to this endpoint. **This endpoint does not update inventory quantities.** It only toggles product visibility on your storefront. * Setting `available: true` makes the product visible and purchasable * Setting `available: false` hides the product from your storefront To manage actual inventory quantities (e.g., setting stock to 50 units), use the [Fourthwall dashboard](https://my.fourthwall.com). The API currently does not support programmatic inventory quantity updates. # Create Promotion Source: https://docs.fourthwall.com/api-reference/platform/promotions/create-promotion POST /open-api/v1.0/promotions *Rate limit: 100 requests / 10 seconds per shop. See [Rate limiting](/guides/rate-limiting).* Creates a promotion **OAuth scope:** `promotions_write` API keys have full access to this endpoint. # Get Promotion Source: https://docs.fourthwall.com/api-reference/platform/promotions/get-promotion GET /open-api/v1.0/promotions/{promotionId} *Rate limit: 100 requests / 10 seconds per shop. See [Rate limiting](/guides/rate-limiting).* Returns a promotion by id **OAuth scope:** `promotions_read` API keys have full access to this endpoint. # List Promotions Source: https://docs.fourthwall.com/api-reference/platform/promotions/list-promotions GET /open-api/v1.0/promotions *Rate limit: 100 requests / 10 seconds per shop. See [Rate limiting](/guides/rate-limiting).* Returns all promotions **OAuth scope:** `promotions_read` API keys have full access to this endpoint. # Get Current Shop Source: https://docs.fourthwall.com/api-reference/platform/shop/get-current-shop GET /open-api/v1.0/shops/current *Rate limit: 100 requests / 10 seconds per shop. See [Rate limiting](/guides/rate-limiting).* Returns the current shop # Get or Create Public Token Source: https://docs.fourthwall.com/api-reference/platform/shop/get-public-token PUT /open-api/v1.0/public-token *Rate limit: 100 requests / 10 seconds per shop. See [Rate limiting](/guides/rate-limiting).* Returns an existing public token for the shop, or creates a new one if none exists **OAuth scope:** `shop_read` API keys have full access to this endpoint. # Get Shop Contact Info Source: https://docs.fourthwall.com/api-reference/platform/shop/get-shop-contact-info GET /open-api/v1.0/shops/current/contact-info *Rate limit: 100 requests / 10 seconds per shop. See [Rate limiting](/guides/rate-limiting).* Returns the current shop contact info **OAuth scope:** `shop_read` API keys have full access to this endpoint. # End Streaming Source: https://docs.fourthwall.com/api-reference/platform/streaming/end-streaming PUT /open-api/v1.0/streaming/end *Rate limit: 100 requests / 10 seconds per shop. See [Rate limiting](/guides/rate-limiting).* Sets streaming status to ended for specified services **OAuth scope:** `giveaway_write` API keys have full access to this endpoint. # Get Streaming Status Source: https://docs.fourthwall.com/api-reference/platform/streaming/get-streaming-status GET /open-api/v1.0/streaming *Rate limit: 100 requests / 10 seconds per shop. See [Rate limiting](/guides/rate-limiting).* Returns streaming status for all services **OAuth scope:** `giveaway_read` API keys have full access to this endpoint. # Start Streaming Source: https://docs.fourthwall.com/api-reference/platform/streaming/start-streaming PUT /open-api/v1.0/streaming/start *Rate limit: 100 requests / 10 seconds per shop. See [Rate limiting](/guides/rate-limiting).* Sets streaming status to started for specified services **OAuth scope:** `giveaway_write` API keys have full access to this endpoint. # Get Thank You Source: https://docs.fourthwall.com/api-reference/platform/thank-yous/get-thank-you GET /open-api/v1.0/thank-yous/{thankYouId} *Rate limit: 100 requests / 10 seconds per shop. See [Rate limiting](/guides/rate-limiting).* Get Thank You details **OAuth scope:** `thankyou_read` API keys have full access to this endpoint. # Create Webhook Source: https://docs.fourthwall.com/api-reference/platform/webhooks/create-webhook POST /open-api/v1.0/webhooks *Rate limit: 100 requests / 10 seconds per shop. See [Rate limiting](/guides/rate-limiting).* Create a webhook **OAuth scope:** `webhook_write` API keys have full access to this endpoint. # Delete Webhook Source: https://docs.fourthwall.com/api-reference/platform/webhooks/delete-webhook DELETE /open-api/v1.0/webhooks/{webhookConfigurationId} *Rate limit: 100 requests / 10 seconds per shop. See [Rate limiting](/guides/rate-limiting).* Delete a webhook **OAuth scope:** `webhook_write` API keys have full access to this endpoint. # Get Webhook Source: https://docs.fourthwall.com/api-reference/platform/webhooks/get-webhook GET /open-api/v1.0/webhooks/{webhookConfigurationId} *Rate limit: 100 requests / 10 seconds per shop. See [Rate limiting](/guides/rate-limiting).* Get a webhook **OAuth scope:** `webhook_read` API keys have full access to this endpoint. # Get Webhook Event Source: https://docs.fourthwall.com/api-reference/platform/webhooks/get-webhook-event GET /open-api/v1.0/webhook-events/{webhookEventId} *Rate limit: 100 requests / 10 seconds per shop. See [Rate limiting](/guides/rate-limiting).* Get a single webhook event by ID **OAuth scope:** `webhook_read` API keys have full access to this endpoint. # List Webhook Events Source: https://docs.fourthwall.com/api-reference/platform/webhooks/list-webhook-events GET /open-api/v1.0/webhook-events *Rate limit: 100 requests / 10 seconds per shop. See [Rate limiting](/guides/rate-limiting).* Get webhook events with pagination and optional filtering by one or more webhook types (repeat or comma-separate the `type` param). **OAuth scope:** `webhook_read` API keys have full access to this endpoint. # List Webhooks Source: https://docs.fourthwall.com/api-reference/platform/webhooks/list-webhooks GET /open-api/v1.0/webhooks *Rate limit: 100 requests / 10 seconds per shop. See [Rate limiting](/guides/rate-limiting).* Get webhooks **OAuth scope:** `webhook_read` API keys have full access to this endpoint. # Update Webhook Source: https://docs.fourthwall.com/api-reference/platform/webhooks/update-webhook PUT /open-api/v1.0/webhooks/{webhookConfigurationId} *Rate limit: 100 requests / 10 seconds per shop. See [Rate limiting](/guides/rate-limiting).* Update a webhook **OAuth scope:** `webhook_write` API keys have full access to this endpoint. # Product created Source: https://docs.fourthwall.com/api-reference/product-events/product-created /openapi/platform.json webhook PRODUCT_CREATED Fired when a new product is created. # Product updated Source: https://docs.fourthwall.com/api-reference/product-events/product-updated /openapi/platform.json webhook PRODUCT_UPDATED Fired when a product is updated. # Promotion created Source: https://docs.fourthwall.com/api-reference/promotion-events/promotion-created /openapi/platform.json webhook PROMOTION_CREATED Fired when a new promotion is created. # Promotion status changed Source: https://docs.fourthwall.com/api-reference/promotion-events/promotion-status-changed /openapi/platform.json webhook PROMOTION_STATUS_CHANGED Fired when a promotion's status changes (e.g., activated, expired). # Promotion updated Source: https://docs.fourthwall.com/api-reference/promotion-events/promotion-updated /openapi/platform.json webhook PROMOTION_UPDATED Fired when a promotion is updated. # Add to Cart Source: https://docs.fourthwall.com/api-reference/storefront/carts/add-to-cart openapi/storefront.json POST /v1/carts/{cartId}/add # Change Cart Quantity Source: https://docs.fourthwall.com/api-reference/storefront/carts/change-cart-quantity openapi/storefront.json POST /v1/carts/{cartId}/change # Create Cart Source: https://docs.fourthwall.com/api-reference/storefront/carts/create-cart openapi/storefront.json POST /v1/carts # Get Cart Source: https://docs.fourthwall.com/api-reference/storefront/carts/get-cart openapi/storefront.json GET /v1/carts/{cartId} # Remove from Cart Source: https://docs.fourthwall.com/api-reference/storefront/carts/remove-from-cart openapi/storefront.json POST /v1/carts/{cartId}/remove # Get Collection Source: https://docs.fourthwall.com/api-reference/storefront/collections/get-collection openapi/storefront.json GET /v1/collections/{slug} Returns a collection # Get Collection Products Source: https://docs.fourthwall.com/api-reference/storefront/collections/get-collection-products openapi/storefront.json GET /v1/collections/{slug}/products Returns all products in the collection # List Collections Source: https://docs.fourthwall.com/api-reference/storefront/collections/list-collections openapi/storefront.json GET /v1/collections Returns all collections # Get Product by Slug Source: https://docs.fourthwall.com/api-reference/storefront/products/get-product openapi/storefront.json GET /v1/products/{slug} # Get Shop Information Source: https://docs.fourthwall.com/api-reference/storefront/shop/get-shop openapi/storefront.json GET /v1/shop # Thank you sent Source: https://docs.fourthwall.com/api-reference/thank-you-events/thank-you-sent /openapi/platform.json webhook THANK_YOU_SENT Fired when a thank you message is sent to a supporter. # App Sections Source: https://docs.fourthwall.com/apps/app-sections Distribute custom iframe sections with your app App Sections are similar to [embedded sections](/embedded-sections/overview), but are distributed with your app. You specify the App Section at the app level, and anyone that installs your app will be able to add it to their shop. ## Signature The signature for app sections works exactly the same as [embedded sections](/embedded-sections/signature), but instead uses your app HMAC secret (also used to verify app webhooks). You can find the app HMAC secret in your [App settings](https://my-shop.fourthwall.com/admin/dashboard/settings/platform-apps?redirect). ## Message passing The messages are different from embedded sections. ### Resize ```json theme={null} { "type": "RESIZE", "data": { "height": 100 } } ``` ### Close ```json theme={null} { "type": "CLOSE" } ``` ## Settings We have an experimental feature that allows you to configure settings for your app section. Contact support on Discord to get access. # Embedded Settings Source: https://docs.fourthwall.com/apps/embedded-settings Provide a settings page for your app users Embedded settings allows you to provide a settings page for users of your app. This will be available when clicking through the app tile on the [apps page](https://my-shop.fourthwall.com/admin/dashboard/apps). This works similarly to [embedded sections](/embedded-sections/overview). ## Getting started You will specify the URL of the iframe that will be rendered on [your app](https://my-shop.fourthwall.com/admin/dashboard/settings/platform-apps?redirect) page, under Settings URL. ## Signature Like embedded sections, this will be called with an HMAC signature. ### Parameters | Parameter | Description | | ----------- | ------------------------------------------------- | | `timestamp` | The timestamp of when the signature was generated | | `shop_id` | The shop ID of the creator using your app | | `hmac` | The HMAC signature | The signature is generated using the following data string: ``` timestamp=${timestamp}&shop_id=${id}&app_id=${app_id} ``` The HMAC here is in base64 vs. hex in the embedded sections. Your HMAC secret key is available in the basic information section of your [Platform App settings](https://my-shop.fourthwall.com/admin/dashboard/settings/platform-apps?redirect). ## JavaScript Example ```javascript theme={null} async function getHmacKey(secret) { const encoder = new TextEncoder(); const keyData = encoder.encode(secret); return await crypto.subtle.importKey( 'raw', keyData, { name: 'HMAC', hash: 'SHA-512' }, false, ['sign', 'verify'] ); } async function verifySignature(params, hmacSignature, secret) { const { shopId, appId, timestamp } = params; const encoder = new TextEncoder(); const key = await getHmacKey(secret); const data = `timestamp=${timestamp}&shop_id=${shopId}&app_id=${appId}`; const messageData = encoder.encode(data); const signature = await crypto.subtle.sign('HMAC', key, messageData); const providedSignature = base64ToUint8Array(hmacSignature); if (signature.byteLength !== providedSignature.length) { return false; } return crypto.subtle.verify('HMAC', key, providedSignature, messageData); } ``` # Getting Started Source: https://docs.fourthwall.com/apps/getting-started Create multi-shop OAuth apps with the Fourthwall Platform ## Overview If you are looking to build an app that will be used by multiple shops, you will need to use OAuth. ## Creating an app Navigate to [Platform apps settings](https://my-shop.fourthwall.com/admin/dashboard/settings/platform-apps?redirect) to create an OAuth app. Here you will set some details for your app, the redirect URLs, and the scopes (permissions) you need for the app. Once you have this information, you are ready to [authenticate as your app](/guides/oauth). # Publishing Your App Source: https://docs.fourthwall.com/apps/publishing Submit your Fourthwall app for review and publication We're still developing an app publishing process. For now, you can submit your app for review by contacting us on Discord. We'll review your app and get back to you with any feedback. ## Before Submitting To make sure your app is ready for review, please make sure to set the following in your app settings: 1. A clear description 2. A Learn More URL that links to a page with more information about your app 3. App Icon Please also be ready with: 1. A video of your app in action 2. A support email that we can reach out to if there are any issues ## Guidelines We will be looking for: * **Value** - Your app must provide value to the community * **Quality** - Your app must be well-tested, well-maintained, and well-supported. We reserve the right to remove broken apps even after they have been published. * **No generative art** - Your app must NOT use generative art. Generative AI is also frowned upon, but we will consider on a case-by-case basis. # Agency integration Source: https://docs.fourthwall.com/channel-api/agency-integration Operate a fleet of subaccount shops with one agency Channel API credential — one secret that authenticates three different ways An **agency integration** lets one operator run merch for many creators from a single console. One confidential `channel.*` credential operates a whole fleet of subaccount shops: you onboard creators, browse and publish to each shop, and surface live storefronts — all from one secret. The worked example for this guide is [**Greenroom**](https://github.com/FourthwallHQ/fourthwall-examples/tree/main/examples/agency), an agency console you can read end to end. The Channel API requires **special access** — it is **invite only** and currently in **beta**. Your channel must be granted access by Fourthwall, which provisions a dedicated agency-bound `channel.*` client for it; [reach out](mailto:support@fourthwall.com) to request access. See [Authentication](/channel-api/authentication). ## One secret, three faces The single agency secret authenticates **three different ways** depending on the call. The secret **never reaches the browser** — every `channel-api` and `open-api` call goes through a server route that mints the token and attaches it. | Face | Authenticates with | Operates on | | --------------- | ------------------------------------ | ----------------------------------- | | **channel-api** | bearer token alone | the agency channel + its bound shop | | **open-api** | bearer token **+ `X-ShopId`** header | one ownership-checked subaccount | | **publicToken** | a browser-safe storefront token | the live shop, read-only | `X-ShopId` is ownership-checked upstream: a missing or unowned shop id is rejected, so you can only ever act on a shop your agency manages. ## The token mint — HTTP Basic, not body credentials The agency uses the **client credentials** grant, sending the id/secret as `client_secret_basic` (HTTP Basic). Posting the secret in the request body returns `401 invalid_client` — the single most common first mistake: ```ts theme={null} const basic = Buffer.from(`${CLIENT_ID}:${CLIENT_SECRET}`).toString("base64"); await fetch(TOKEN_URL, { method: "POST", headers: { Authorization: `Basic ${basic}`, "Content-Type": "application/x-www-form-urlencoded", }, body: new URLSearchParams({ grant_type: "client_credentials" }), }); ``` Cache the token until shortly before it expires, then mint a fresh one. ## Two clients over one token * **`channelApi`** — attaches `Authorization: Bearer ` only. * **`openApi`** — attaches the bearer **and** the `X-ShopId` header for the target subaccount. ## Five features, mapped to the three faces Identify the connected agency and list its fleet of shops. `GET /channel-api/v1.0/channel/current` + `GET /channel-api/v1.0/shops` Create a subaccount shop, invite the owner, and optionally start payout onboarding. The creation response carries the new shop's `publicToken`. `POST /channel-api/v1.0/shops` Browse one shop's products, scoped by `X-ShopId`. `GET /open-api/v1.0/products` with `X-ShopId: ` The load-bearing flow — it touches **both** faces. See the next section. Resolve the shop's browser-safe `publicToken` server-side, then read the live shop directly from the browser against the Storefront API — no agency secret in play. `PUT /open-api/v1.0/public-token` (server) → Storefront API (browser) ## F4 — why artwork is registered twice F4 renders an instant preview on the channel's bound shop, then publishes on the subaccount. The artwork is registered **twice, across two faces**: ```mermaid theme={null} flowchart TD subgraph Preview [Preview — channel-api, bound shop] A[upload-url] --> B[PUT bytes] --> C[media/images] --> D[previews] end subgraph Publish [Publish — open-api + X-ShopId, subaccount] E[media/upload-url] --> F[PUT bytes] --> G[media/images again] --> H[products type=design] --> I[products/id/state PUBLIC] end D --> E ``` The bound-shop `imageId` from the preview is **not** valid for the design-create on the subaccount — that resolves the `imageId` against the `X-ShopId` shop, so the publish path must register the media again on the subaccount. Gotchas the example pins down: * **The `region` is template-specific.** The renderer rejects an invalid region and returns the valid ones — `front` for DTG, `front_dtf` for DTFX, etc. Derive the region from the template's `productionMethod`. * **`colors` / `sizes` are case-sensitive** against the product's variant labels, or the render comes back empty. Leave them empty to use all available. * **`publishOnCreate`** controls whether the product is created `PUBLIC` or `HIDDEN`. When off, transition it to `PUBLIC` via `PUT /products/{id}/state` after creation. * **The presigned upload `PUT` must echo two signed headers** — `Content-Type` and `x-goog-content-length-range: 0,` (the byte length declared in the `upload-url` request) — or GCS returns `403 SignatureDoesNotMatch`. ## F5 — the publicToken face The `publicToken` is **browser-safe**. Resolve it server-side (`GET /api/shops/{shopId}/public-token` → `PUT /open-api/v1.0/public-token`) and then read the live shop directly from the browser against the [Storefront API](/storefront/overview) — no agency secret in play. The token comes from F2's creation response (for shops created in-session) or the public-token endpoint (for any managed shop). Onboarding creates **real shops and real payouts**. Target **staging** by default while building, and treat a payout account as *pending* after the handoff — returning from the `returnUrl` means the creator left the hosted flow, not that payouts are verified. ## Full example Read [**Greenroom**](https://github.com/FourthwallHQ/fourthwall-examples/tree/main/examples/agency) end to end — a Next.js console where `lib/fourthwall.ts` holds the credential core (token mint + the two clients) and every Fourthwall call runs through a server route. # Authentication Source: https://docs.fourthwall.com/channel-api/authentication Exchange your channel client credentials for a Channel API access token The Channel API is invite only. This page is for partners who have already been issued a `channel.*` client ID and secret by Fourthwall. For access to Channel API as an agency, please contact [support@fourthwall.com](mailto:support@fourthwall.com) For access to Channel API as a platform, please contact [platform-partnerships@fourthwall.com](mailto:platform-partnerships@fourthwall.com) Each channel is issued its own OAuth2 client and authenticates with the **client credentials** grant. For an overview of what the Channel API is for and the full onboarding flow, see the [Channel API Integration Guide](/channel/integration-guide). ## 1. Request an access token Exchange your client ID and secret for a short-lived access token at the Fourthwall token endpoint. Credentials are sent with the **client\_secret\_basic** method — that is, as HTTP Basic Auth on the token request itself, not in the form body. ```bash cURL theme={null} curl -X POST \ "https://auth.fourthwall.com/auth/realms/Fourthwall/protocol/openid-connect/token" \ -u "$CHANNEL_CLIENT_ID:$CHANNEL_CLIENT_SECRET" \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=client_credentials" ``` ```javascript JavaScript theme={null} const clientId = process.env.CHANNEL_CLIENT_ID; const clientSecret = process.env.CHANNEL_CLIENT_SECRET; const basic = Buffer.from(`${clientId}:${clientSecret}`).toString("base64"); const res = await fetch( "https://auth.fourthwall.com/auth/realms/Fourthwall/protocol/openid-connect/token", { method: "POST", headers: { Authorization: `Basic ${basic}`, "Content-Type": "application/x-www-form-urlencoded", }, body: new URLSearchParams({ grant_type: "client_credentials" }), }, ); const { access_token } = await res.json(); ``` Send the client ID and secret in the `Authorization: Basic` header, not in the request body. Submitting them as body parameters returns `401 invalid_client`. The response contains an `access_token` and its `expires_in` lifetime. Tokens are short-lived — request a new one when the current token expires rather than caching it indefinitely. ## 2. Call the Channel API Pass the access token as a **Bearer** token on every Channel API request. ```bash cURL theme={null} curl "https://api.fourthwall.com/channel-api/v1.0/channel/current" \ -H "Authorization: Bearer $ACCESS_TOKEN" ``` Use `https://auth.fourthwall.com` and `https://api.fourthwall.com` for production. For testing against staging, swap in `https://auth.staging.fourthwall.com` and `https://api.staging.fourthwall.com`. # Platform integration Source: https://docs.fourthwall.com/channel-api/platform-integration Build a single-creator platform on the Channel API — provision a shop behind the scenes and create real products from your own UI A **platform integration** embeds Fourthwall merch directly into a product a creator already uses — a link-in-bio page, a creator dashboard, a community tool. The creator never leaves your UI: they browse blank products, drop in artwork, see a live preview, and publish, while your backend provisions a Fourthwall shop and creates real products on their behalf. The worked example for this guide is [**Linkstand**](https://github.com/FourthwallHQ/fourthwall-examples/tree/main/examples/partner-platform), a product-first "links admin" where every row on the page is a real Fourthwall product created through one guided wizard. The Channel API requires **special access** — it is **invite only** and currently in **beta**. Your channel must be granted access by Fourthwall, which provisions a dedicated `channel.*` client for it; [reach out](mailto:support@fourthwall.com) to request access. See [Authentication](/channel-api/authentication). ## The defining idea: the shop appears on publish Browsing templates, uploading artwork, and rendering a preview are all **shop-less** — they go straight through the Channel API on the credential alone. Only on **publish** does a shop come into play: your backend provisions one (once) and creates the live product against it. > Preview is shop-less; create is shop-bound. | Wizard step | What happens | API call | Shop? | | ----------------------------- | ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ | ------------------ | | 1 · Pick a product | Browse blank product templates | `GET /open-api/v1.0/product-templates/page/{page}` | No | | 2 · Add artwork | Presigned upload, then register the image | `POST /channel-api/v1.0/upload-url` → `POST /channel-api/v1.0/media/images` | No | | 3 · Preview | Render preview images synchronously | `POST /channel-api/v1.0/previews` | No | | 4 · Details & price → Publish | Provision a shop on first publish, then create the product | `GET /channel-api/v1.0/shops` → (first time) `POST /channel-api/v1.0/shops` → `POST /open-api/v1.0/products` | Creates / needs it | ## Two credential faces, one channel The same channel credential authenticates two API surfaces: * **channel-api** (`/channel-api/v1.0/…`) — authorized by the channel bearer **alone**. Everything shop-less: template browsing, upload, preview, shop lookup, and shop provisioning. * **open-api** (`/open-api/v1.0/…`) — the product-templates list is shop-less, but product **create / list / state / delete** are shop-bound: the same bearer plus an `X-ShopId` header selects the shop you provisioned. ## Architecture: a thin backend-for-frontend Keep the credential on your server. Each `/api/*` route is a small proxy that attaches the channel credential and forwards to one (or, for publish, a few) Fourthwall endpoints. The browser only ever talks to your own routes. ``` Creator → your app (browser) → /api/* (server, holds credential) → Fourthwall ``` Linkstand maps its routes one-to-one onto Fourthwall: | Your route | Forwards to | Shop? | | --------------------------------- | ------------------------------------------------------------------ | ------------- | | `GET /api/templates` | `GET /open-api/v1.0/product-templates/page/1` | No | | `GET /api/channel` | `GET /channel-api/v1.0/channel/current` | No | | `POST /api/upload-url` | `POST /channel-api/v1.0/upload-url` | No | | `POST /api/media` | `POST /channel-api/v1.0/media/images` | No | | `POST /api/previews` | `POST /channel-api/v1.0/previews` | No | | `POST /api/publish` | shop lookup → create (first only) → `POST /open-api/v1.0/products` | **Needs one** | | `GET /api/links` | `GET /channel-api/v1.0/shops` → `GET /open-api/v1.0/products` | Needs one | | `PUT /api/links/{id}/visibility` | `PUT /open-api/v1.0/products/{id}/state` | Needs one | | `DELETE /api/links/{id}` | `DELETE /open-api/v1.0/products/{id}` | Needs one | ## The flow, step by step Install your channel app and exchange the OAuth code for the channel credential, holding it server-side. Confirm the connection by reading the current channel: ```bash cURL theme={null} curl "https://api.fourthwall.com/channel-api/v1.0/channel/current" \ -H "Authorization: Bearer $ACCESS_TOKEN" ``` List blank product templates and let the creator choose one. This is shop-less: ```bash cURL theme={null} curl "https://api.fourthwall.com/open-api/v1.0/product-templates/page/1" \ -H "Authorization: Bearer $ACCESS_TOKEN" ``` Request a presigned upload URL, `PUT` the bytes, then register the image to get an `imageId`. Both calls are on the channel face — no shop yet: ```bash cURL theme={null} # 1. presigned URL curl -X POST "https://api.fourthwall.com/channel-api/v1.0/upload-url" \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "fileName": "art.png", "contentType": "image/png" }' # 2. PUT the bytes to the returned uploadUrl (echo the signed headers) # 3. register → imageId curl -X POST "https://api.fourthwall.com/channel-api/v1.0/media/images" \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "fileUrl": "" }' ``` Render preview mockups synchronously from the chosen template + `imageId`. Still shop-less: ```bash cURL theme={null} curl -X POST "https://api.fourthwall.com/channel-api/v1.0/previews" \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "productId": "", "imageId": "" }' ``` Publish is the only step that needs a shop, and it owns the whole shop lifecycle so your client makes a single call: 1. `GET /channel-api/v1.0/shops` — does the channel have a shop yet? 2. `POST /channel-api/v1.0/shops` — **only on first publish** (idempotent: a second publish reuses the same shop, never a duplicate). 3. `POST /open-api/v1.0/products` with the `X-ShopId` header — create the live product from the **same** `productId` + `imageId` the preview was rendered from. ```bash cURL theme={null} curl -X POST "https://api.fourthwall.com/open-api/v1.0/products" \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H "X-ShopId: $SHOP_ID" \ -H "Content-Type: application/json" \ -d '{ "type": "design", "name": "My link", "...": "..." }' ``` Thread the previewed `productId` + `imageId` through your wizard state and reuse them at publish — don't re-derive the design. The previewed design and the published product are then guaranteed to be the same. ## Managing published products Once a shop exists, manage the catalog on the open-api face with `X-ShopId`: * **Show / hide** → `PUT /open-api/v1.0/products/{id}/state` (`PUBLIC` / `HIDDEN`) * **Archive** → `DELETE /open-api/v1.0/products/{id}` * **List** → `GET /open-api/v1.0/products` ## Full example Read [**Linkstand**](https://github.com/FourthwallHQ/fourthwall-examples/tree/main/examples/partner-platform) end to end — a Next.js app-router project where the BFF in `lib/fourthwall.ts` is the only module that holds the credential and talks to Fourthwall. # Channel API Integration Guide Source: https://docs.fourthwall.com/channel/integration-guide Provision Fourthwall shops and onboard creators programmatically from your platform via the Channel API The Channel API is Fourthwall's **shop provisioning and onboarding** layer. It lets your platform create Fourthwall shops for your creators and onboard them end to end — spin up a shop, invite its owner, set up payouts, and receive the credentials your platform needs to operate that shop on the creator's behalf. It answers one question: *how does a new creator get a working Fourthwall shop from inside your product?* Everything downstream — designing products, building a storefront, taking checkout — is handled by the [Platform API](/api-reference/platform) and [Storefront API](/storefront/overview) once the shop exists. This guide is about getting that shop created and the creator onboarded. The Channel API requires **special access**. It is **invite only** and currently in **beta** — there is no self-serve way to obtain credentials. Your channel must be explicitly granted access by Fourthwall, which then provisions a dedicated `channel.*` client (id + secret) for it. [Reach out to Fourthwall](mailto:support@fourthwall.com) to request access before you build. See [Authentication](/channel-api/authentication). ## Onboarding flow ```mermaid theme={null} flowchart TD A[Creator starts setup on your platform] --> B{New or existing shop?} B -->|New| C[POST /channel-api/v1.0/shops] C --> D[Shop created — shopId + publicToken returned] C --> E[Owner invited by email] C --> F[Payout onboarding URL returned] F --> G[Redirect creator to hosted payout onboarding] G --> H[Creator returns to your returnUrl] B -->|Existing| I[Creator authenticates via OAuth and links their shop] D --> J[Shop ready — operate it via Platform & Storefront APIs] H --> J I --> J ``` A creator either gets a **brand-new shop** your platform provisions for them, or **links an existing** Fourthwall shop through OAuth. The Channel API owns the first path — creating and onboarding the new shop. ## Authentication The Channel API uses OAuth2 **client credentials**. Your channel client authenticates with its own id and secret; no per-creator authorization step is required to create shops. ```bash cURL theme={null} curl -X POST \ "https://auth.fourthwall.com/auth/realms/Fourthwall/protocol/openid-connect/token" \ -u "$CHANNEL_CLIENT_ID:$CHANNEL_CLIENT_SECRET" \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=client_credentials" ``` Pass the returned `access_token` as a Bearer token on every Channel API request. Full details — including the HTTP Basic gotcha — are on the [Authentication](/channel-api/authentication) page. ## Creating a shop A single call provisions the shop, invites the owner, and (optionally) kicks off payout onboarding: ```bash cURL theme={null} curl -X POST "https://api.fourthwall.com/channel-api/v1.0/shops" \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "Nova'\''s Shop", "ownerEmail": "nova@example.com", "payout": { "returnUrl": "https://yourapp.com/onboarding/return", "refreshUrl": "https://yourapp.com/onboarding/refresh", "country": "US", "businessType": "individual" } }' ``` ```json Response theme={null} { "shopId": "sh_abc123", "shopName": "Nova's Shop", "publicToken": "ptkn_xyz789", "invitationEmail": "nova@example.com", "invitationStatus": "INVITED", "payoutOnboardingUrl": "https://connect.fourthwall.com/onboard/..." } ``` | Field | Required | What it does | | ------------ | -------- | ----------------------------------------------------------------------------------------------------------------------- | | `name` | Yes | Display name for the new shop. | | `ownerEmail` | No | Sends an owner invitation to this address. Also used as the payout account email when no payout prefill email is given. | | `payout` | No | Provisions a connected payout account and returns a hosted onboarding URL. | ## Inviting the owner Pass `ownerEmail` and Fourthwall sends an owner invitation to the creator. The response echoes `invitationEmail` and an `invitationStatus` (`INVITED`, `FAILED`, …). Accepting the invite is how the creator gains direct dashboard access to the shop your platform created for them. ## Payout onboarding Include a `payout` object and Fourthwall provisions a connected payout account and returns a single-use `payoutOnboardingUrl`. Redirect the creator there to complete identity and bank details in Fourthwall's hosted flow. * `returnUrl` — where the creator lands when they leave the hosted flow. * `refreshUrl` — where they land if the link has expired or been reused; mint a fresh shop/payout link and redirect again. * `country` / `businessType` — drive the required verification fields. * `prefill` — optional values to pre-fill the form so the creator types less. Returning to your `returnUrl` means the creator **left** the hosted flow — not that the account is verified. Treat the payout account as *pending* and check its status separately before relying on payouts. ## What you get back | Field | Use it for | | --------------------- | -------------------------------------------------------------------------------------- | | `shopId` | Scope subsequent Platform API calls to this shop (e.g. the `X-ShopId` header). | | `publicToken` | Browser-safe [Storefront API](/storefront/overview) reads for the shop. | | `payoutOnboardingUrl` | Redirect the creator to finish payout setup (present only when `payout` was supplied). | ## Shop OAuth tokens This feature is **planned but not yet available**. The following describes the intended behavior. When you create a shop, the response will also include OAuth credentials for it: ```json theme={null} { "shopId": "sh_abc123", "shopName": "Nova's Shop", "publicToken": "ptkn_xyz789", "accessToken": "eyJhbGciOi...", "refreshToken": "dGhpcyBpcyBh..." } ``` These let your platform call the Platform API **on behalf of the created shop** without a separate per-creator OAuth authorization step. The `accessToken` is short-lived; use the `refreshToken` to renew it. ## After the shop exists Onboarding is done once the shop is created and the creator is invited. From there: * **Manage the shop** — products, orders, analytics — with the [Platform API](/api-reference/platform), scoped to the `shopId`. * **Display the storefront** — read products and drive checkout with the [Storefront API](/storefront/overview) using the `publicToken`. For worked, end-to-end integrations see the [Platform integration](/channel-api/platform-integration) (single creator) and [Agency integration](/channel-api/agency-integration) (a fleet of shops) guides. Across every onboarded shop, Fourthwall acts as **Merchant of Record** — handling fulfillment, shipping, tax compliance, payment disputes, and customer support — so your platform owns onboarding, not operations. # Messages Source: https://docs.fourthwall.com/embedded-sections/messages PostMessage API for embedded sections From inside the iframe (on the page you've provided), you can pass certain messages to the parent frame to trigger actions. ## Supported Events ### Resize Dynamically resize the iframe height: ```json theme={null} { "type": "resize", "height": 100 } ``` ### Close Close or hide the embedded section: ```json theme={null} { "type": "close" } ``` # Embedded Sections Source: https://docs.fourthwall.com/embedded-sections/overview Embed secure iframe sections into your Fourthwall shop Embedded sections are a way to embed content sections into your Fourthwall shop. These sections use HMAC signatures to verify that the content is being embedded on a Fourthwall shop, and can be gated by memberships. For app sections that can be distributed with your app, see [App Sections](/apps/app-sections). Embedded sections allow you to extend your Fourthwall shop with custom content that is securely embedded and verified. This is particularly useful for: * **Live stream overlays** - Display real-time order notifications, supporter shoutouts, or donation goals during your streams * **Customer support tools** - Embed live chat widgets, FAQ sections, or ticket submission forms directly in your shop * **Social proof elements** - Show recent purchases, customer reviews, or social media feeds to build trust * **Membership gated content** - Create exclusive content sections that only appear for paying members ## How it works An embedded section is rendered as an iframe on your shop with certain query parameters added to the URL to prove that it is being embedded for: 1. A specific Fourthwall shop 2. A specific supporter (for memberships) 3. A specific tier (for memberships) First generate a secret key in [Developer settings](https://my-shop.fourthwall.com/admin/dashboard/settings/for-developers#embedded-hmac-secret?redirect). Without setting the secret key, the iframe will not be rendered. You can add it as a section like any other section in your shop. Embedded section example Once added, you will need to configure a URL for the iframe. Embedded section configuration Now whenever someone visits your shop, the iframe will be called with a signature. # Signature Verification Source: https://docs.fourthwall.com/embedded-sections/signature Verify embedded section authenticity using HMAC-SHA512 ## Overview To ensure the authenticity of embedded sections, Fourthwall uses HMAC-SHA512 signatures to prove that the content is being embedded on a Fourthwall shop, for a specific supporter and tier. ## Parameters The following parameters are passed to your iframe: | Parameter | Description | | -------------- | ------------------------------------------------- | | `hmac` | The HMAC signature | | `timestamp` | The timestamp of when the signature was generated | | `shop_id` | Your shop ID | | `supporter_id` | Supporter ID of the logged-in member | | `tier_id` | Tier ID of the supporter's subscription | ## Verification To verify the signature, recalculate the HMAC signature using the same parameters and your secret key. The data string format: ``` shop_id=${shop_id}&supporter_id=${supporter_id}&tier_id=${tier_id}×tamp=${timestamp} ``` Your secret key is available in your [Developer settings](https://my-shop.fourthwall.com/admin/dashboard/settings/for-developers#embedded-hmac-secret?redirect). **Never leak this secret in your code.** If exposed, it can be used to impersonate any supporter on your shop. ## JavaScript Example ```javascript theme={null} async function getHmacKey(secret) { const encoder = new TextEncoder(); const keyData = encoder.encode(secret); return await crypto.subtle.importKey( 'raw', keyData, { name: 'HMAC', hash: 'SHA-512' }, false, ['sign', 'verify'] ); } async function verifySignature(params, hmacSignature, secret) { const { shopId, supporterId, tierId, timestamp } = params; const encoder = new TextEncoder(); const key = await getHmacKey(secret); const data = `shop_id=${shopId}&supporter_id=${supporterId}&tier_id=${tierId}×tamp=${timestamp}`; const messageData = encoder.encode(data); const signature = await crypto.subtle.sign('HMAC', key, messageData); const generatedSignatureHex = uint8ArrayToHex(new Uint8Array(signature)); return generatedSignatureHex === hmacSignature; } ``` # Authentication Source: https://docs.fourthwall.com/guides/authentication Authenticate with shop-level API keys using Basic Access Authentication **Which authentication method should you use?** * **Basic Auth** - Use this if you are building integrations for **your own shop**. This is the most common use case and what most developers need. Simply create API credentials and include them with each request. * **OAuth** - Use this only if you are building an app that will be used by **multiple different shops** (e.g., a third-party integration that other Fourthwall creators will install). See the [OAuth guide](/guides/oauth) for details. The simplest way to authenticate is with a shop level API key. This key will give you unrestricted access to all API endpoints for your shop. ## Getting credentials The creation of API credentials is reserved for users with the **SUPER ADMIN** role. 1. Navigate to [For developers](https://my-shop.fourthwall.com/admin/dashboard/settings/for-developers?redirect) to create an API user. 2. If API credentials haven't been generated yet, you will find a "Create API User" button under the "Open API" section. Click on it, and shortly after, the Username and Password for the Open API User will be provided. Keep your API credentials confidential and do not share them with unauthorized personnel. These credentials grant access to sensitive data and actions within our system. Always use HTTPS/SSL for encrypted communication when making API requests. ## Authorizing with credentials After Open API User was created, you can authorize your request by using **Basic Access Authentication**. This can be achieved by constructing an **Authorization** header with the format **Basic base64-encoded-username-and-password**. The base64-encoded credentials should be passed with each request to gain access to the protected resources. ```bash cURL theme={null} curl -u "your_username:your_password" https://api.fourthwall.com/open-api/v1/order/{YOUR_ORDER_ID} ``` ```javascript JavaScript theme={null} const username = "your_username"; const password = "your_password"; // Combine username and password with a colon const combinedCredentials = `${username}:${password}`; // Encode the combined credentials to Base64 const base64Credentials = btoa(combinedCredentials); const apiUrl = "https://api.fourthwall.com/open-api/v1/order/{YOUR_ORDER_ID}"; const requestOptions = { method: "GET", headers: { "Authorization": `Basic ${base64Credentials}`, "Content-Type": "application/json" } }; fetch(apiUrl, requestOptions) .then(response => response.json()) .then(data => { console.log(data); }) .catch(error => { console.error("Error:", error); }); ``` # Create design products Source: https://docs.fourthwall.com/guides/create-design-products Create design products with the Platform API, including the image upload → register → imageId → create design flow A **design product** runs your artwork through the design pipeline: it renders each region's image onto a product template (a tee, mug, hoodie, …) and creates a purchasable product. Create one with the [Create a product](/api-reference/platform/products/create-product) endpoint (`POST /open-api/v1.0/products`) by setting `type: "design"`. Looking to sell downloadable files instead? See [Create digital products](/guides/create-digital-products). **OAuth scope:** `offer_write` for creating products, plus `media_write` for the image upload steps below. API keys have full access to these endpoints. See [Authentication](/guides/authentication) for API keys or [OAuth](/guides/oauth) for multi-shop apps. A design product is built from one or more **regions** (for example `front` and `back`), each rendered from an image you supply. You don't pass an image URL directly — instead you upload the image, **register** it in your media library to obtain an `imageId`, and reference that id per region when you create the product. ```mermaid theme={null} flowchart LR A[Request upload URL] --> B[Upload image bytes] B --> C[Register image → imageId] C --> D[Create product with regions imageId] ``` Each design product renders onto a product template (the blank tee, mug, etc.). List the available templates with [`GET /open-api/v1.0/product-templates`](/api-reference/platform/product-templates/list-product-templates) and note the `id` you want — you'll pass it as `productTemplateId`. Call [`POST /open-api/v1.0/media/upload-url`](/api-reference/platform/media-library/request-media-upload-url) with the file's metadata. The response contains a short-lived `uploadUrl` to `PUT` the bytes to, and the `fileUrl` you'll register in the next step. **`size` is the file's exact length in bytes** — read it from the file rather than hardcoding it, and hold on to the value: you must send the **same** number again in the `x-goog-content-length-range` header when you PUT the bytes (step 3). Compute it from whatever you're uploading: * **Node (Buffer):** `fileBytes.byteLength` (or `(await fs.promises.stat("my-design.png")).size` without reading the file) * **Browser (`File`/`Blob`):** `file.size` * **Shell:** `wc -c < my-design.png` ```bash cURL theme={null} # size must be the file's exact byte count — derive it, don't guess SIZE=$(wc -c < my-design.png) curl -u "your_username:your_password" \ -X POST https://api.fourthwall.com/open-api/v1.0/media/upload-url \ -H "Content-Type: application/json" \ -d "{ \"fileName\": \"my-design.png\", \"contentType\": \"image/png\", \"size\": $SIZE }" ``` ```javascript JavaScript theme={null} const credentials = btoa("your_username:your_password"); const fileBytes = await fs.promises.readFile("my-design.png"); const size = fileBytes.byteLength; // exact byte count — reused in the PUT below const res = await fetch("https://api.fourthwall.com/open-api/v1.0/media/upload-url", { method: "POST", headers: { "Authorization": `Basic ${credentials}`, "Content-Type": "application/json" }, body: JSON.stringify({ fileName: "my-design.png", contentType: "image/png", size }) }); const { uploadUrl, fileUrl } = await res.json(); ``` ```json Response theme={null} { "uploadUrl": "https://storage.googleapis.com/...&X-Goog-Signature=...", "fileUrl": "https://cdn.fourthwall.com/media/.../my-design.png" } ``` `PUT` the raw image bytes to the `uploadUrl`. This request goes **directly to Google Cloud Storage**, not to Fourthwall — do **not** send your Fourthwall credentials with it. The URL is signed with two conditions you **must** match exactly, or GCS rejects the upload with `403 SignatureDoesNotMatch`: * **`Content-Type`** — must equal the `contentType` you sent in step 2 (here, `image/png`). * **`x-goog-content-length-range: 0,`** — must use the **same `size`** (byte count) you sent in step 2. Reuse the exact value; don't recompute it a different way. The `x-goog-content-length-range` header is required and easy to miss — it's baked into the URL's signature. Omitting it (or sending a different size) fails with `403 SignatureDoesNotMatch` even when everything else looks right. The `uploadUrl` is short-lived (\~6 hours); if it expires, request a new one. ```bash cURL theme={null} # $SIZE is the same value you sent to /media/upload-url in step 2 curl -X PUT "" \ -H "Content-Type: image/png" \ -H "x-goog-content-length-range: 0,$SIZE" \ --data-binary @my-design.png ``` ```javascript JavaScript theme={null} // Reuse fileBytes and size from step 2 — same bytes, same byte count. await fetch(uploadUrl, { method: "PUT", headers: { "Content-Type": "image/png", // Required: the same `size` you sent to /media/upload-url. Baked into // the signature — a different value fails with 403 SignatureDoesNotMatch. "x-goog-content-length-range": `0,${size}` }, body: fileBytes }); ``` Now persist the uploaded image in your media library with [`POST /open-api/v1.0/media/images`](/api-reference/platform/media-library/save-media-image). Pass the `fileUrl` from step 2 along with the image's pixel dimensions. The response's `id` is the **`imageId`** you'll reference per region. ```bash cURL theme={null} curl -u "your_username:your_password" \ -X POST https://api.fourthwall.com/open-api/v1.0/media/images \ -H "Content-Type: application/json" \ -d '{ "fileUrl": "https://cdn.fourthwall.com/media/.../my-design.png", "width": 2400, "height": 2400 }' ``` ```javascript JavaScript theme={null} const res = await fetch("https://api.fourthwall.com/open-api/v1.0/media/images", { method: "POST", headers: { "Authorization": `Basic ${credentials}`, "Content-Type": "application/json" }, body: JSON.stringify({ fileUrl, width: 2400, height: 2400 }) }); const image = await res.json(); console.log(image.id); // e.g. "img_k66ZW4fsRm6c2def3itltA" — your imageId ``` ```json Response theme={null} { "id": "img_k66ZW4fsRm6c2def3itltA", "uri": "https://cdn.fourthwall.com/media/.../my-design.png", "width": 2400, "height": 2400, "thumbnail": "https://cdn.fourthwall.com/...", "preview": "https://cdn.fourthwall.com/..." } ``` Register an image once and reuse its `imageId` across multiple regions or multiple products — there's no need to re-upload the same artwork. Finally, call [`POST /open-api/v1.0/products`](/api-reference/platform/products/create-product) with `type: "design"`. Each entry in `regions` pairs a product `region` with the `imageId` you just registered. ```bash cURL theme={null} curl -u "your_username:your_password" \ -X POST https://api.fourthwall.com/open-api/v1.0/products \ -H "Content-Type: application/json" \ -d '{ "type": "design", "productTemplateId": "pro_k66ZW4fsRm6c2def3itltA", "name": "My Awesome Design Tee", "description": "Limited edition design", "regions": [ { "region": "front", "imageId": "img_k66ZW4fsRm6c2def3itltA", "placementStrategy": "AUTO" } ], "publishOnCreate": false }' ``` ```javascript JavaScript theme={null} const res = await fetch("https://api.fourthwall.com/open-api/v1.0/products", { method: "POST", headers: { "Authorization": `Basic ${credentials}`, "Content-Type": "application/json" }, body: JSON.stringify({ type: "design", productTemplateId: "pro_k66ZW4fsRm6c2def3itltA", name: "My Awesome Design Tee", description: "Limited edition design", regions: [ { region: "front", imageId: image.id, placementStrategy: "AUTO" } ], publishOnCreate: false }) }); const product = await res.json(); ``` ```json Response theme={null} { "productId": "off_a1b2c3d4e5f6", "customizationId": "cus_a1b2c3d4e5f6", "images": [ { "url": "https://cdn.fourthwall.com/.../front-black.png", "width": 1200, "height": 1200, "style": "Unisex Tee", "color": "Black", "size": null, "region": "front" } ] } ``` Pass the registered image's **`id`** (the `imageId`) in `regions[].imageId` — not the `uploadUrl` or `fileUrl`. An `imageId` that isn't a registered media-library image is rejected with a validation error; register it via `POST /open-api/v1.0/media/images` first. ### Placement strategies By default each region uses `placementStrategy: "AUTO"`, which lets the renderer apply the product's automation defaults. Set it explicitly to control how the image is placed: | Strategy | Behavior | | -------------- | ------------------------------------------------------------------------------------------------------------------------- | | `AUTO` | Let the renderer decide using the product's defaults (preferred placement, or fill-all for items like mugs and stickers). | | `FILL_ALL` | Apply the image to every placement in the region. | | `FULL_REGION` | Render the image across the full region, skipping the preferred placement. | | `PLACEMENT_ID` | Target a single placement named by `placementId` (required for this strategy). | You can also limit which `colors` and `sizes` are rendered, and set a `profitMargin` (a USD amount, e.g. `10.00`) on top of the base cost. Products are created hidden unless you set `publishOnCreate: true`. ## Next steps Full request and response schema for `POST /products`. Upload and register images to reference by `imageId`. Browse templates to find a `productTemplateId`. Sell downloadable files with a creator-set price. # Create digital products Source: https://docs.fourthwall.com/guides/create-digital-products Create a digital product with the Platform API, then attach downloadable files and images before publishing A **digital product** is sold at a creator-set price (in USD), ready for you to attach downloadable files and images. Create one with the [Create a product](/api-reference/platform/products/create-product) endpoint (`POST /open-api/v1.0/products`) by setting `type: "digital"`. Selling printed merch from your own artwork instead? See [Create design products](/guides/create-design-products). **OAuth scope:** `offer_write` for creating the product and managing its digital files. Attaching product images also needs `media_write`, since the image bytes are uploaded through the media library first. API keys have full access to these endpoints. See [Authentication](/guides/authentication) for API keys or [OAuth](/guides/oauth) for multi-shop apps. ## Create the product Send `type: "digital"` with a creator-set `price` (a USD amount). Products default to hidden. Set `publishOnCreate: true` to make the product visible on the storefront as soon as it's created; otherwise it stays hidden and can only be published from the Fourthwall admin. ```bash cURL theme={null} curl -u "your_username:your_password" \ -X POST https://api.fourthwall.com/open-api/v1.0/products \ -H "Content-Type: application/json" \ -d '{ "type": "digital", "name": "My Digital Product", "description": "A detailed description of my digital product", "price": 25.00 }' ``` ```javascript JavaScript theme={null} const credentials = btoa("your_username:your_password"); const res = await fetch("https://api.fourthwall.com/open-api/v1.0/products", { method: "POST", headers: { "Authorization": `Basic ${credentials}`, "Content-Type": "application/json" }, body: JSON.stringify({ type: "digital", name: "My Digital Product", description: "A detailed description of my digital product", price: 25.0 }) }); const product = await res.json(); console.log(product.productId); // e.g. "" ``` ```json Response theme={null} { "productId": "", "customizationId": null, "images": [] } ``` ## Attach downloadable files These are the files your buyers download after purchase. Unlike media-library images — which are shop-wide and reusable across products by `imageId` — **a digital file belongs to the one product you upload it against**. The upload URL is scoped to a `productId`, the bytes are stored under that product, and confirming links the file to it. The flow mirrors the [media upload](/guides/create-design-products) used for design artwork — request a pre-signed URL, `PUT` the bytes to Google Cloud Storage, then confirm — but it's a product-scoped endpoint and the confirm step links the file to the product instead of registering a reusable image. Digital files are size-limited per shop: **25 MB** on free plans, up to **1 GB** otherwise. The `size` you send is enforced both when the URL is issued and by the signed URL itself — sending more bytes than declared fails the upload. **`size` is the file's exact length in bytes** — read it from the file rather than hardcoding it, and hold on to the value: you must send the **same** number again in the `x-goog-content-length-range` header when you PUT the bytes (step 2). Compute it from whatever you're uploading: * **Node (Buffer):** `fileBytes.byteLength` (or `(await fs.promises.stat("my-ebook.pdf")).size` without reading the file) * **Browser (`File`/`Blob`):** `file.size` * **Shell:** `wc -c < my-ebook.pdf` Call [`POST /open-api/v1.0/products/{productId}/digital-files/upload-url`](/api-reference/platform/digital-products/request-digital-file-upload-url) with the file's metadata. The response is the same `{ uploadUrl, fileUrl }` shape as the media flow: a short-lived `uploadUrl` to `PUT` the bytes to, and the `fileUrl` you'll confirm in the last step. ```bash cURL theme={null} # size must be the file's exact byte count — derive it, don't guess SIZE=$(wc -c < my-ebook.pdf) curl -u "your_username:your_password" \ -X POST https://api.fourthwall.com/open-api/v1.0/products//digital-files/upload-url \ -H "Content-Type: application/json" \ -d "{ \"fileName\": \"my-ebook.pdf\", \"contentType\": \"application/pdf\", \"size\": $SIZE }" ``` ```javascript JavaScript theme={null} const credentials = btoa("your_username:your_password"); const fileBytes = await fs.promises.readFile("my-ebook.pdf"); const size = fileBytes.byteLength; // exact byte count — reused in the PUT below const res = await fetch( `https://api.fourthwall.com/open-api/v1.0/products/${productId}/digital-files/upload-url`, { method: "POST", headers: { "Authorization": `Basic ${credentials}`, "Content-Type": "application/json" }, body: JSON.stringify({ fileName: "my-ebook.pdf", contentType: "application/pdf", size }) } ); const { uploadUrl, fileUrl } = await res.json(); ``` ```json Response theme={null} { "uploadUrl": "https://storage.googleapis.com/...&X-Goog-Signature=...", "fileUrl": "", "expiresAt": "2026-05-28T12:34:56Z" } ``` `expiresAt` is when the pre-signed `uploadUrl` stops accepting PUTs; request a new one if it lapses. `PUT` the raw bytes to the `uploadUrl`. This goes **directly to Google Cloud Storage**, not to Fourthwall — do **not** send your Fourthwall credentials with it. The URL is signed with the same two conditions as the media upload, and missing either fails with `403 SignatureDoesNotMatch`: * **`Content-Type`** — must equal the `contentType` you sent in step 1 (here, `application/pdf`). * **`x-goog-content-length-range: 0,`** — must use the **same `size`** (byte count) you sent in step 1. Reuse the exact value; don't recompute it a different way. The `x-goog-content-length-range` header is required and easy to miss — it's baked into the URL's signature. Omitting it (or sending a different size) fails with `403 SignatureDoesNotMatch` even when everything else looks right. The `uploadUrl` is short-lived; if it expires, request a new one. ```bash cURL theme={null} # $SIZE is the same value you sent to /digital-files/upload-url in step 1 curl -X PUT "" \ -H "Content-Type: application/pdf" \ -H "x-goog-content-length-range: 0,$SIZE" \ --data-binary @my-ebook.pdf ``` ```javascript JavaScript theme={null} // Reuse fileBytes and size from step 1 — same bytes, same byte count. await fetch(uploadUrl, { method: "PUT", headers: { "Content-Type": "application/pdf", // Required: the same `size` you sent to /digital-files/upload-url. Baked // into the signature — a different value fails with 403 SignatureDoesNotMatch. "x-goog-content-length-range": `0,${size}` }, body: fileBytes }); ``` Link the uploaded file to the product with [`POST /open-api/v1.0/products/{productId}/digital-files`](/api-reference/platform/digital-products/confirm-digital-file-upload). Pass the `fileUrl` from step 1 and the `fileName` buyers will see. The response is the full, updated product. ```bash cURL theme={null} curl -u "your_username:your_password" \ -X POST https://api.fourthwall.com/open-api/v1.0/products//digital-files \ -H "Content-Type: application/json" \ -d '{ "fileUrl": "", "fileName": "my-ebook.pdf" }' ``` ```javascript JavaScript theme={null} const res = await fetch( `https://api.fourthwall.com/open-api/v1.0/products/${productId}/digital-files`, { method: "POST", headers: { "Authorization": `Basic ${credentials}`, "Content-Type": "application/json" }, body: JSON.stringify({ fileUrl, fileName: "my-ebook.pdf" }) } ); const product = await res.json(); ``` Buyers receive digital files as downloads (they're served with `Content-Disposition: attachment`). Repeat this flow once per file to attach several downloads to the same product. To remove one, call [`DELETE /open-api/v1.0/products/{productId}/digital-files`](/api-reference/platform/digital-products/remove-digital-file) with its `fileUrl`. ## Attach product images Product images are the showcase thumbnails shown on the storefront — separate from the downloadable files above. They are **not** uploaded through the digital-files endpoint. Instead, upload the image through the shop-wide [media library](/api-reference/platform/media-library/request-media-upload-url) (`POST /open-api/v1.0/media/upload-url`, then `PUT` the bytes — exactly as in [Create design products](/guides/create-design-products)), then attach the resulting URL to the product by its pixel dimensions: ```bash cURL theme={null} curl -u "your_username:your_password" \ -X POST https://api.fourthwall.com/open-api/v1.0/products//images \ -H "Content-Type: application/json" \ -d '{ "images": [ { "url": "", "width": 800, "height": 600 } ] }' ``` Pass the `fileUrl` you uploaded as `url`; `width` and `height` are the image's pixel dimensions. ## Next steps Full request and response schema for `POST /products`. Upload and confirm the downloadable files customers receive. Add showcase images to a digital product. Render your artwork onto tees, mugs, and more. # OAuth Authentication Source: https://docs.fourthwall.com/guides/oauth OAuth 2.0 authentication flow for multi-shop apps If you are looking to build an app that will be used by multiple shops, you will need to use OAuth. First, create an app by following the instructions in [Apps - Getting started](/apps/getting-started). Once completed, you will need to know: 1. Your redirect URL 2. Your client ID 3. Your client secret ## Authorize URL Link your users to this URL to start the login process for your app. Always use `my-shop.fourthwall.com` as the shop URL as this will link to the logged in user's current shop. You can copy this URL from the OAuth tab of your app. You will need to provide your Client ID in the path (you can get this from your apps settings page), your redirect URL, as well as an optional state parameter. ``` https://my-shop.fourthwall.com/admin/platform-apps//connect?redirect_uri=&state= ``` ## Getting an access token After the user has authorized your app, they will be redirected to your redirect URL with an authorization code and the state parameter if you provided one. You will need to exchange this code for an access token. ```bash theme={null} curl -XPOST https://api.fourthwall.com/open-api/v1.0/platform/token \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=authorization_code&redirect_uri=&client_id=&client_secret=&code=" ``` The response will contain an `access_token`, `refresh_token`, and other information. ## Using the access token ```bash theme={null} curl -XGET https://api.fourthwall.com/open-api/v1/order/{YOUR_ORDER_ID} \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" ``` ## Refreshing the access token Access tokens expire rather quickly (within a few minutes). You can use the `refresh_token` to get a new access token without having to re-authorize the user. ```bash theme={null} curl -XPOST https://api.fourthwall.com/open-api/v1.0/platform/token \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=refresh_token&client_id=&client_secret=&refresh_token=" ``` # Platform API Overview Source: https://docs.fourthwall.com/guides/overview RESTful API for managing your Fourthwall shop programmatically The Fourthwall Platform API is a RESTful API that allows you to manage various aspects of your shop. It can be accessed either directly using your own API key (this gives you full access to your own shop) or indirectly through OAuth for multi-shop apps. ## OpenAPI Specs Full OpenAPI specifications are available in both JSON and YAML formats: | Spec | JSON | YAML | | -------------- | ------------------------------------------------- | ------------------------------------------------- | | Platform API | [open-api.json](/open-api-docs/open-api.json) | [open-api.yaml](/open-api-docs/open-api.yaml) | | Storefront API | [storefront.json](/open-api-docs/storefront.json) | [storefront.yaml](/open-api-docs/storefront.yaml) | Use these to generate client SDKs, import into tools like Postman, or integrate with any OpenAPI-compatible tooling. ## Authentication Methods Direct shop access with Basic Auth credentials Multi-shop app authentication with access tokens # Rate Limiting Source: https://docs.fourthwall.com/guides/rate-limiting API rate limits, per-endpoint buckets, and how to handle 429 responses Most platform endpoints share a default limit of **100 requests per 10 seconds, per shop**. A handful of write-heavy endpoints have tighter, dedicated limits. Each endpoint's reference page shows the limit that applies to it at the top. When you exceed a limit the API responds with `429 Too Many Requests`: ```json theme={null} { "code": "OPEN_API_TOO_MANY_REQUESTS", "title": "Too many requests", "status": 429 } ``` ## Limits | Limit | Applies to | | ------------------- | ---------------------------------------- | | **100 req / 10s** | Default for all Platform API endpoints | | **5 req / minute** | `POST /products`, `POST /customizations` | | **20 req / minute** | `POST /media/upload-url` | All limits are counted **per shop** — different shops have independent quotas. When multiple limits apply to the same endpoint, the tightest one wins. Limits are subject to change. The per-endpoint banner on each reference page is the authoritative source for the limit currently in force. # Examples Source: https://docs.fourthwall.com/html/examples Complete working examples of Custom Code sections These examples demonstrate real-world patterns for building Custom Code sections. Each example is a complete, working section you can use as a starting point. ## Featured collection A product grid that displays products from a specific collection with a heading and "View all" link. ```liquid theme={null} {%- assign collection = collections['all'] -%}

{{ collection.title }}

{% for product in collection.products limit: 8 %} {% if product.featured_image %} {{ product.title }} {% endif %}

{{ product.title }}

{{ product.price | money }}

{% endfor %}
``` *** ## Membership tiers A responsive grid displaying all membership tiers with pricing, features, and signup links. ```liquid theme={null}

Membership Tiers

{% for tier in membership_tiers %}
{% if tier.type == 'Free' %} {% if tier.image_url %} {{ tier.name }} {% endif %}

{{ tier.name }}

Free

Join now {% if tier.perks.size > 0 %}
    {% for perk in tier.perks %}
  • {{ perk }}
  • {% endfor %}
{% endif %} {% elsif tier.type == 'Paid' %} {% if tier.image %} {{ tier.name }} {% endif %}

{{ tier.name }}

{% if tier.compare_at_monthly_price %} {{ tier.monthly_price | money }} {{ tier.compare_at_monthly_price | money }} first month {% else %} {{ tier.monthly_price | money }} / month {% endif %}

{% if tier.trial_enabled %} Start free trial {% else %} Join now {% endif %} {% if tier.annual_price %}

Save {{ tier.annual_discount_percentage }}% annually

{% endif %} {% if tier.features.size > 0 %}
    {% for feature in tier.features %}
  • {{ feature }}
  • {% endfor %}
{% endif %} {% endif %}
{% endfor %}
``` *** ## Image with text A two-column layout combining an image with text content and a call-to-action button. ```liquid theme={null} {%- assign collection = collections['featured'] -%} {%- assign product = collection.products | first -%}
{% if product.featured_image %}
{{ product.title }}
{% endif %}

{{ product.title }}

{% if product.description != blank %}
{{ product.description }}
{% endif %} View product — {{ product.price | money }}
``` # Array & String Filters Source: https://docs.fourthwall.com/html/filters/array-string Filter arrays and transform strings ## Array filters In addition to standard Liquid array filters (`first`, `last`, `size`, `sort`, `map`, `join`, etc.): | Filter | Description | Example | | ------- | -------------------------------- | -------------------------------------------- | | `where` | Filter by property value | `{{ products \| where: 'available', true }}` | | `limit` | Return first N items | `{% for p in products \| limit: 4 %}` | | `push` | Add item to array (non-mutating) | `{{ array \| push: item }}` | ## String filters In addition to standard Liquid string filters (`upcase`, `downcase`, `capitalize`, `strip`, etc.): | Filter | Description | Example | | ---------------------- | ------------------------ | --------------------------------------------- | | `camelize` | Convert to CamelCase | `{{ 'my-class' \| camelize }}` → `MyClass` | | `handle` / `handleize` | Convert to URL-safe slug | `{{ 'My Title!' \| handleize }}` → `my-title` | # Image Filters Source: https://docs.fourthwall.com/html/filters/image Generate optimized image URLs with predefined sizes ## img\_url Generate optimized image URLs using predefined size shortcuts. ```liquid theme={null} {{ product.featured_image | img_url: 'large' }} ``` ### Available sizes | Shortcode | Size (px) | | --------- | --------- | | `pico` | 96 | | `icon` | 128 | | `thumb` | 160 | | `small` | 220 | | `medium` | 320 | | `large` | 480 | | `grande` | 600 | | `display` | 720 | | `jumbo` | 1024 | | `master` | 1440 | | `desktop` | 1920 | | `ultra` | 3000 | ### Usage examples ```liquid theme={null} {{ product.featured_image | img_url: 'thumb' }} {{ product.featured_image | img_url: 'large' }} {{ product.featured_image | img_url: 'desktop' }} ``` ### Responsive images ```liquid theme={null} {{ product.title }} ``` # Money Filters Source: https://docs.fourthwall.com/html/filters/money Format monetary values as currency strings Format monetary values as currency strings. All money filters accept a price value and return a formatted string using the shop's currency. | Filter | Description | Example | | ------------------------------ | ------------------------ | ------------------------------------------------------------- | | `money` | Standard currency format | `{{ product.price \| money }}` → `$25.00` | | `money_without_currency` | Hide currency symbol | `{{ product.price \| money_without_currency }}` → `25.00` | | `money_without_cents_if_whole` | Omit cents when `.00` | `{{ product.price \| money_without_cents_if_whole }}` → `$25` | ## No-exchange money filters These variants format the price without applying currency conversion: | Filter | Description | | ------------------------------------------ | ---------------------------------- | | `money_no_exchange` | Format without currency conversion | | `money_no_exchange_without_currency` | Without exchange, no symbol | | `money_no_exchange_without_cents_if_whole` | Without exchange, omit `.00` | ## Money arithmetic ```liquid theme={null} {{ product.price | money_times: 2 }} ``` # Filters Source: https://docs.fourthwall.com/html/filters/overview Liquid filters available in Fourthwall Custom Code sections Filters modify the output of Liquid objects. They are placed within an output tag `{{ }}` and are separated by a pipe character `|`. All [standard Liquid filters](https://shopify.github.io/liquid/filters/) are available, plus the Fourthwall-specific filters listed below. Format prices and currency values. Generate localized URLs and transform strings to handles. Generate optimized image URLs with predefined sizes. Filter, limit, push arrays. Camelize and handleize strings. Date formatting. All standard Liquid filters for strings, math, arrays, and more. # Standard Liquid Filters Source: https://docs.fourthwall.com/html/filters/standard All standard Liquid filters for strings, math, arrays, and more All standard Liquid filters work as documented in the [official Liquid reference](https://shopify.github.io/liquid/filters/). ## String filters `append`, `capitalize`, `downcase`, `upcase`, `escape`, `lstrip`, `rstrip`, `strip`, `newline_to_br`, `prepend`, `remove`, `remove_first`, `replace`, `replace_first`, `slice`, `split`, `strip_html`, `strip_newlines`, `truncate`, `truncatewords`, `url_decode`, `url_encode` ## Math filters `abs`, `at_most`, `at_least`, `ceil`, `divided_by`, `floor`, `minus`, `modulo`, `plus`, `round`, `times` ## Array filters `compact`, `concat`, `first`, `join`, `last`, `map`, `reverse`, `size`, `sort`, `sort_natural`, `uniq`, `where` ## Other filters `date`, `default` # URL Filters Source: https://docs.fourthwall.com/html/filters/url Generate localized URLs, asset paths, and HTML tags | Filter | Description | Example | | ---------------------- | -------------------------- | ---------------------------------------------------------------- | | `localized_url` | Add locale prefix to URL | `{{ product.url \| localized_url }}` → `/en/products/my-product` | | `url_encode` | URL-encode a string | `{{ 'hello world' \| url_encode }}` → `hello+world` | | `url_escape` | Escape URL characters | `{{ url \| url_escape }}` | | `handle` / `handleize` | Convert to URL-safe handle | `{{ 'My Product!' \| handle }}` → `my-product` | # Utility Filters Source: https://docs.fourthwall.com/html/filters/utility Date formatting | Filter | Description | Example | | ------ | ---------------- | ----------------------------------------------- | | `date` | Format date/time | `{{ product.created_at \| date: '%B %d, %Y' }}` | ## date Formats dates using `strftime` syntax in the shop's timezone. ```liquid theme={null} {{ product.created_at | date: '%B %d, %Y' }} {{ product.published_at | date: '%m/%d/%Y' }} ``` # collection Source: https://docs.fourthwall.com/html/objects/collection Access product collections and their products Access product collections through the global `collections` object using a collection handle. ## Accessing a collection ```liquid theme={null} {%- assign collection = collections['all'] -%} {% for product in collection.products limit: 8 %} {{ product.title }} {% endfor %} ``` ## Collection properties | Property | Type | Description | | ---------------- | ------- | -------------------------- | | `title` | string | Collection name | | `handle` | string | URL-safe identifier | | `products` | array | Products in the collection | | `products_count` | integer | Number of products | ## Example ```liquid theme={null} {%- assign collection = collections['all'] -%}

{{ collection.title }}

{% for product in collection.products limit: 8 %}
{% if product.featured_image %} {{ product.title }} {% endif %}

{{ product.title }}

{{ product.price | money }}

View
{% endfor %}
``` # membership_tiers Source: https://docs.fourthwall.com/html/objects/membership-tiers Access membership tier information, pricing, and features Access membership tier information through the `membership_tiers` object. This contains all configured tiers for the shop, both free and paid. ## Iterating over tiers ```liquid theme={null} {% for tier in membership_tiers %}

{{ tier.name }}

{% if tier.type == 'Paid' %}

{{ tier.monthly_price | money }} per month

{% else %}

Free

{% endif %}
{% endfor %} ``` Each tier has a `type` property that is either `"Free"` or `"Paid"`. Use this to branch your rendering logic. ## Free tier properties | Property | Type | Description | | ------------------ | ------- | ----------------------------------- | | `type` | string | Always `"Free"` | | `name` | string | Tier display name | | `description` | string | Tier description | | `position` | integer | Display order | | `image_url` | string | Tier image URL | | `members_count` | integer | Number of members | | `perks` | array | List of perk descriptions (strings) | | `registration_url` | string | Signup URL | ## Paid tier properties | Property | Type | Description | | --------------- | ------- | -------------------------------------- | | `type` | string | Always `"Paid"` | | `name` | string | Tier display name | | `description` | string | Tier description | | `position` | integer | Display order | | `image` | string | Tier image URL | | `members_count` | integer | Number of members | | `features` | array | List of feature descriptions (strings) | | `url` | string | Tier purchase URL | | `trial_enabled` | boolean | Whether free trial is available | Free tiers use `image_url` for the image, while paid tiers use `image`. Free tiers list benefits as `perks`, while paid tiers use `features`. ## Paid tier pricing | Property | Type | Description | | ---------------------------- | ------- | -------------------------------------------------- | | `monthly_price` | money | Monthly subscription price | | `monthly_variant` | variant | Monthly billing variant | | `compare_at_monthly_price` | money | Original monthly price (for first-month discounts) | | `annual_price` | money | Annual subscription price | | `annual_variant` | variant | Annual billing variant | | `annual_offer_id` | string | Annual offer identifier | | `annual_url` | string | Annual subscription URL | | `annual_discount_enabled` | boolean | Whether annual discount is active | | `annual_discount_percentage` | integer | Annual discount percentage | | `compare_at_annual_price` | money | Original annual price | ### Displaying prices ```liquid theme={null} {% if tier.compare_at_monthly_price %} {{ tier.monthly_price | money }} {{ tier.compare_at_monthly_price | money }} first month {% else %} {{ tier.monthly_price | money }} per month {% endif %} ``` ## Variant discounts Each tier variant may have discount information for specific promotions: ```liquid theme={null} {{ tier.monthly_variant.discounts.twitch.after_discount_price | money }} {{ tier.monthly_variant.discounts.introductory.after_discount_price | money }} ``` ### Twitch discount properties | Property | Type | Description | | --------------------------------------- | ----- | ------------------------------- | | `discounts.twitch.after_discount_price` | money | Price after Twitch sub discount | ### Introductory discount properties | Property | Type | Description | | --------------------------------------------- | -------- | --------------------------------- | | `discounts.introductory.started_at` | datetime | Discount start date | | `discounts.introductory.ended_at` | datetime | Discount end date | | `discounts.introductory.after_discount_price` | money | Price after introductory discount | ## Example: Membership tiers grid ```liquid theme={null} {% for tier in membership_tiers %}
{% if tier.type == 'Free' %} {% if tier.image_url %} {{ tier.name }} {% endif %}

{{ tier.name }}

{% if tier.members_count %}

{{ tier.members_count }} member{% if tier.members_count != 1 %}s{% endif %}

{% endif %}

Free

Join now {% if tier.perks.size > 0 %}
    {% for perk in tier.perks %}
  • {{ perk }}
  • {% endfor %}
{% endif %} {% elsif tier.type == 'Paid' %} {% if tier.image %} {{ tier.name }} {% endif %}

{{ tier.name }}

{% if tier.compare_at_monthly_price %} {{ tier.monthly_price | money }} {{ tier.compare_at_monthly_price | money }} first month {% else %} {{ tier.monthly_price | money }} per month {% endif %}

{% if tier.trial_enabled %} Start 7-day free trial {% else %} Join now {% endif %} {% if tier.annual_price %}

Save {{ tier.annual_discount_percentage }}% if you pay annually

{% endif %} {% if tier.features.size > 0 %}
    {% for feature in tier.features %}
  • {{ feature }}
  • {% endfor %}
{% endif %} {% endif %}
{% endfor %} ``` # Available Objects Source: https://docs.fourthwall.com/html/objects/overview Data objects available in Fourthwall Custom Code sections Custom Code sections have access to a specific set of objects that provide data about your shop's collections, products, and membership tiers. ## Available objects Product collections and their contents. Individual product data, pricing, images, and variants. Membership tier information, pricing, and features. ## Quick overview | Object | Description | Access | | ------------------ | ------------------------ | -------------------------------------- | | `collections` | All product collections | `collections[handle]` | | `product` | Product data | Iterating over a collection's products | | `membership_tiers` | All membership tiers | Direct global access | | `section` | Current section instance | `section.id` | # product Source: https://docs.fourthwall.com/html/objects/product Access product data including pricing, images, and variants Product data is available when iterating over a collection's products. ```liquid theme={null} {%- assign collection = collections['all'] -%} {% for product in collection.products limit: 8 %} {{ product.title }} — {{ product.price | money }} {% endfor %} ``` ## Product types Every product has a `type` field that determines which properties are available. The available types are: | Type | Description | | ---------- | ------------------------------------------- | | `Standard` | Regular products with variants and options | | `Combined` | Combined products with variants and options | | `GiftCard` | Gift card products | | `Bundle` | A bundle of multiple products sold together | Bundle products have a different set of properties than Standard, Combined, and GiftCard products. Properties marked with specific type availability are only present on those product types. ## Common properties These properties are available on **all** product types. | Property | Type | Description | | ------------------------- | ------- | -------------------------------------------------------------------------- | | `title` | string | Product name | | `handle` | string | URL-safe identifier | | `description` | string | Full product description (HTML) | | `url` | string | Product page URL | | `available` | boolean | Whether the product is in stock | | `type` | string | Product type (`Standard`, `Combined`, `GiftCard`, `Bundle`) | | `featured_image` | image | Main product image | | `images` | array | All product images | | `price` | money | Current price (default variant for Single types, bundle price for Bundles) | | `compare_at_price` | money | Original price (for sales) | | `promoted_price` | money | Promotional price (if active) | | `members_only` | boolean | Whether product requires membership | | `available_for_all_tiers` | boolean | Available to all membership tiers | | `required_tiers` | array | Tiers required to purchase | | `gift` | boolean | Whether the product is a gift | | `metafields` | object | Custom metafields | | `promotion_badges` | array | Active promotion badges | | `promotion_description` | string | Promotion description text | | `promotion_descriptions` | array | All promotion descriptions | ## Standard, Combined, and GiftCard properties These properties are only available when `product.type` is `Standard`, `Combined`, or `GiftCard`. ### Variants | Property | Type | Description | | --------------------- | ------- | ---------------------------------------- | | `variants` | array | All product variants | | `options` | array | Option names (e.g., `["Size", "Color"]`) | | `options_with_values` | array | Options with their possible values | | `default_variant` | variant | The default variant | Each variant object has: | Property | Type | Description | | ------------------ | ------- | --------------------------- | | `price` | money | Variant price | | `compare_at_price` | money | Original price (for sales) | | `available` | boolean | Whether variant is in stock | | `option1` | string | First option value | | `option2` | string | Second option value | | `option3` | string | Third option value | | `image` | image | Variant-specific image | | `images` | array | All variant images | ### Additional pricing | Property | Type | Description | | ----------- | ----- | -------------------- | | `price_min` | money | Lowest variant price | ### Other | Property | Type | Description | | ------------- | -------- | ---------------------------------- | | `collections` | array | Collections the product belongs to | | `updated_at` | datetime | When the product was last updated | ## Bundle properties These properties are only available when `product.type` is `Bundle`. | Property | Type | Description | | ------------------ | ------ | ---------------------------------------------------------------------------------------------------- | | `offers` | array | The individual products included in the bundle (each is a Standard/Combined/GiftCard product object) | | `pricing_strategy` | object | How the bundle is priced | ### Pricing strategy The `pricing_strategy.strategy` object has a `type` field indicating the pricing model: | Strategy type | Description | Additional fields | | -------------------- | ------------------------------------------------ | ------------------------------------------------- | | `FIXED_PRICE` | Bundle has a fixed price | `strategy.price.value`, `strategy.price.currency` | | `DISCOUNT_BASED` | Bundle applies a percentage discount | `strategy.discount_percentage` | | `SAME_AS_INDIVIDUAL` | Bundle price equals the sum of individual prices | — | ```liquid theme={null} {% if product.type == 'Bundle' %} {% assign strategy = product.pricing_strategy.strategy %} {% if strategy.type == 'DISCOUNT_BASED' %} {{ strategy.discount_percentage }}% off when bought together {% endif %}

Includes:

    {% for offer in product.offers %}
  • {{ offer.title }} — {{ offer.price | money }}
  • {% endfor %}
{% endif %} ``` ## Images ### Image object properties Each image object has: | Property | Type | Description | | -------------- | ------- | -------------------- | | `url` | string | Image URL | | `alt` | string | Alt text | | `width` | integer | Width in pixels | | `height` | integer | Height in pixels | | `aspect_ratio` | float | Width / height ratio | Use the [`img_url` filter](/html/filters/image) to generate optimized image URLs: ```liquid theme={null} {{ product.featured_image | img_url: 'large' }} ``` ## Formatting prices Use [money filters](/html/filters/money) to format prices: ```liquid theme={null} {{ product.price | money }} {{ product.compare_at_price | money }} ``` ## Membership restrictions ```liquid theme={null} {% if product.members_only %} {% if product.available_for_all_tiers %}

Available for all members

{% else %}

Available for: {% for tier in product.required_tiers %} {{ tier.name }}{% unless forloop.last %}, {% endunless %} {% endfor %}

{% endif %} {% endif %} ``` ## Promotions Each badge has `type` and `label` properties: ```liquid theme={null} {% if product.promotion_badges %} {% for badge in product.promotion_badges %} {{ badge.label }} {% endfor %} {% endif %} ``` ## Example: Product card ```liquid theme={null}
{% if product.featured_image %} {{ product.title }} {% endif %} {% if product.promotion_badges %} {% for badge in product.promotion_badges %} {{ badge.label }} {% endfor %} {% endif %}

{{ product.title }}

{% if product.compare_at_price %} {{ product.compare_at_price | money }} {% endif %} {{ product.price | money }}
{% if product.type == 'Bundle' %}

{{ product.offers.size }} items in this bundle

{% endif %} {% if product.available %} View product {% else %}

Out of stock

{% endif %}
``` # Custom Code Sections Source: https://docs.fourthwall.com/html/overview Build custom storefront sections using HTML with Liquid templating Custom Code sections allow you to build fully custom sections for your Fourthwall shop using HTML combined with [Liquid](https://shopify.github.io/liquid/) templating. You can add these sections to your shop through the theme editor. ## What is Liquid? Liquid is an open-source template language. It uses a combination of **objects**, **tags**, and **filters** to load dynamic content into templates. ```liquid theme={null} {{ product.title }} {% if product.available %}

In stock!

{% endif %} {{ product.price | money }} ``` ## How it works When you add a Custom Code section to your shop, you write an `.html` file that contains three parts: 1. **Styles** — CSS scoped to the section 2. **Markup** — HTML combined with Liquid logic 3. **Script** — JavaScript scoped to the section instance ## Available objects In a Custom Code section, you have access to: | Object | Description | | ------------------ | ------------------------------------------------------ | | `collections` | All product collections in the shop | | `product` | Individual product data (accessed through collections) | | `membership_tiers` | Membership tier information | | `section` | The current section instance | ## Liquid reference guide For comprehensive Liquid language documentation, refer to this resource: * [Liquid template language](https://shopify.github.io/liquid/) — Official open-source Liquid documentation with basics, tags, and filters All standard Liquid features documented in this guide work in Fourthwall, including: * **Tags**: `if`/`elsif`/`else`, `unless`, `case`/`when`, `for`, `assign`, `capture`, `increment`, `raw`, `comment` * **Filters**: `append`, `capitalize`, `downcase`, `upcase`, `replace`, `split`, `strip`, `truncate`, `size`, `sort`, `map`, `where`, `first`, `last`, `join`, `plus`, `minus`, `times`, `divided_by`, `modulo`, `round`, `abs`, `date`, and more ## Next steps Learn how to structure a section with styles, markup, and script. Explore the data objects available in your templates. See all filters for formatting data. Complete working examples of HTML sections. # Section Structure Source: https://docs.fourthwall.com/html/section-structure How to structure a Custom Code section with styles, markup, and script Every Custom Code section consists of three parts: **styles**, **markup**, and **script**. Together, these give you full control over appearance, content, and behavior. ## Basic structure ```liquid theme={null}

My Custom Section

{%- assign collection = collections['all'] -%} {% for product in collection.products limit: 4 %}

{{ product.title }}

{{ product.price | money }}

{% endfor %}
``` ## Styles Place your CSS inside a ` ``` Always use `section.id` in your CSS selectors. Without it, styles from one section instance will leak into others. ## Markup The markup section contains HTML mixed with Liquid tags and objects. This is where you define the visual structure and dynamic content of your section. ```liquid theme={null}

Featured Products

{%- assign collection = collections['all'] -%} {% for product in collection.products limit: 8 %}
{{ product.title }}

{{ product.title }}

{{ product.price | money }}

View product
{% endfor %}
``` ### Key objects in markup | Object | Description | | ------------------ | -------------------------------------------- | | `section` | The current section instance | | `section.id` | Unique identifier for this section instance | | `collections` | All product collections (accessed by handle) | | `membership_tiers` | All membership tiers | ## Script Place your JavaScript inside a ` ``` Always scope DOM queries to the section instance. Using `document.querySelector('.toggle')` without a section root will match elements across every instance of the section on the page. ## Best practices ### Scope CSS with section.id Always include `section.id` in class names to prevent style conflicts: ```liquid theme={null}

Featured Products

``` ### Use responsive design Apply mobile-first styles with media queries: ```liquid theme={null} ``` ### Check for blank values Always check if values exist before rendering: ```liquid theme={null} {% if product.featured_image %} {{ product.title }} {% endif %} {% if product.compare_at_price %} {{ product.compare_at_price | money }} {% endif %} ``` ### Limit product iterations Always use the `limit` filter when iterating over product collections to control performance: ```liquid theme={null} {%- assign collection = collections['all'] -%} {% for product in collection.products limit: 8 %}
{{ product.title }}
{% endfor %} ``` # Introduction Source: https://docs.fourthwall.com/index Build integrations with the Fourthwall platform Welcome to the Fourthwall developer documentation. Build custom integrations, automate your shop, or create apps for the Fourthwall ecosystem. ## What can you build? Manage orders, products, memberships, and more with full API access. Build custom storefronts with public shop and product data. Receive real-time notifications for orders, subscriptions, and events. Create apps that integrate directly into creator dashboards. ## Ready to start? Get your API key and make your first API call in minutes. # Quickstart Source: https://docs.fourthwall.com/quickstart Make your first API call in minutes Get up and running with the Fourthwall API in three steps. ## 1. Get your API credentials Creating API credentials requires the **SUPER ADMIN** role. 1. Go to [Settings > For Developers](https://my-shop.fourthwall.com/admin/dashboard/settings/for-developers?redirect) 2. Under "Open API", click **Create API User** 3. Save the username and password that are generated ## 2. Make your first API call Test your credentials by fetching your shop details. The Fourthwall API uses **Basic Authentication** - simply pass your username and password with each request. ```bash cURL theme={null} # Basic Auth - the simplest way to authenticate curl -u "your_username:your_password" \ https://api.fourthwall.com/open-api/v1.0/shops/current ``` ```javascript JavaScript theme={null} const username = "your_username"; const password = "your_password"; // Basic Auth: base64 encode "username:password" const credentials = btoa(`${username}:${password}`); fetch("https://api.fourthwall.com/open-api/v1.0/shops/current", { headers: { "Authorization": `Basic ${credentials}` } }) .then(res => res.json()) .then(data => console.log(data)); ``` ```python Python theme={null} import requests from requests.auth import HTTPBasicAuth # Basic Auth is built into the requests library response = requests.get( "https://api.fourthwall.com/open-api/v1.0/shops/current", auth=HTTPBasicAuth("your_username", "your_password") ) print(response.json()) ``` The `-u` flag in cURL automatically handles Basic Auth encoding for you. Under the hood, it creates an `Authorization: Basic ` header. You should receive a response with your shop details: ```json theme={null} { "id": "sh_abc123", "name": "My Shop", "currency": "USD", ... } ``` ## 3. Explore the API Now that you're authenticated, explore what you can build: Fetch orders from your shop Get your product catalog Get real-time event notifications Browse all available endpoints # Community Source: https://docs.fourthwall.com/resources/community Join the Fourthwall developer community Come chat with us on [Discord](https://discord.com/invite/kc6P68z3Uz)! ## Useful Channels * **#developers-general** - General developer discussion and questions * **#creator-resources** - Resources and tools for creators # Storefront Showcase Source: https://docs.fourthwall.com/resources/showcase Example custom storefronts built with Fourthwall Check out these example storefronts built with the Fourthwall Storefront API: ## Featured Storefronts Official Next.js starter kit for building custom storefronts ## Build Your Own Ready to build your own custom storefront? Get started with our [Storefront API documentation](/storefront/overview). # Cart Checkout Endpoint Source: https://docs.fourthwall.com/shop-apis/cart-checkout-endpoint Full reference for /cart/checkout?products=... direct checkout links Use this endpoint to send a shopper directly into Fourthwall checkout from a URL. It is especially useful when you want to pass product variants directly (`products=...`) from ads, feeds, or custom links. Base URL: ```text theme={null} https://{shop_domain}/cart/checkout ``` Recommended direct-products example: ```text theme={null} https://{shop_domain}/cart/checkout?products=11111111-1111-1111-1111-111111111111:1,22222222-2222-2222-2222-222222222222:2¤cy=USD ``` If you already have a Storefront API cart, `cartId` is also supported: ```text theme={null} https://{shop_domain}/cart/checkout?cartId=22222222-2222-2222-2222-222222222222¤cy=USD ``` ## Request * Method: `GET` * Path: `/cart/checkout` * Intended use: Browser redirect links (ads, email, social, product feeds, etc.) * Use the root path (`/cart/checkout`), not a locale-prefixed path. ## Query Parameters | Param | Required | Description | | --------------------------------------------------------- | -------- | -------------------------------------------------------------------------- | | `products` | No | Comma-separated list of cart items in `variantId[:quantity]` format. | | `cartId` | No | Existing cart ID to checkout. If present and valid, `products` is ignored. | | `coupon` | No | Promo code to apply. | | `currency` | No | Checkout currency (for example `USD`). | | `cart_origin` | No | Cart origin metadata. | | `utm_*`, `_ga`, `_fbp`, `_fbc`, `gclid`, `fbclid`, `FPID` | No | Marketing params are captured and propagated in checkout metadata. | ## `products` Format Format: ```text theme={null} products=variantId[:quantity],variantId[:quantity],... ``` Rules: * `variantId` must be a variant UUID (not product/offer ID). * `quantity` is optional; default is `1`. * Invalid quantity values fall back to `1`. * Entries with too many separators (like `id:1:extra`) are skipped. * URL-encoded input is supported. * If any entry has an invalid variant UUID, parsing fails and `products` becomes empty. Examples: ```text theme={null} ?products=11111111-1111-1111-1111-111111111111 ?products=11111111-1111-1111-1111-111111111111:2 ?products=11111111-1111-1111-1111-111111111111:1,22222222-2222-2222-2222-222222222222:3 ``` ## Behavior Checkout creation flow: 1. If `products` contains valid variant entries, the endpoint can create/update a cart from those products. 2. If `cartId` is provided and valid, checkout uses that cart and ignores `products`. 3. If both are unavailable (no valid `cartId` and no valid `products`), redirect goes to `/`. 4. On success, a checkout is created/updated and the shopper is redirected to `/checkout/{checkoutId}`. Promo code precedence: 1. `coupon` query param 2. Manually-applied promo already on cart 3. Automatically applied promo code from a shared link Currency precedence: 1. `currency` query param 2. Session currency 3. `USD` ## Response This endpoint responds with redirects: * Success: `303 See Other` -> `/checkout/{checkoutId}` * Empty/invalid input: `303 See Other` -> `/` * Exception path: `303 See Other` -> `/?error_message=...` ## Google Merchant Center Template This endpoint supports the Google template pattern: ```text theme={null} https://{shop_domain}/cart/checkout?products={id}:1 ``` ## Example Integration Direct variant handoff (primary): ```javascript theme={null} function redirectToDirectCheckout(shopDomain, variantId, quantity = 1, coupon) { const params = new URLSearchParams({ products: `${variantId}:${quantity}` }); if (coupon) params.set("coupon", coupon); window.location.href = `https://${shopDomain}/cart/checkout?${params.toString()}`; } ``` Cart ID handoff (also supported): ```javascript theme={null} function redirectCartToCheckout(shopDomain, cartId, currency = "USD") { const params = new URLSearchParams({ cartId, currency }); window.location.href = `https://${shopDomain}/cart/checkout?${params.toString()}`; } ``` # Shop Feeds Source: https://docs.fourthwall.com/shop-apis/shop-feeds RSS and JSON product feeds for shop integration ## Products RSS feed If you're just looking to read public product information, you do not need to use the Open API. All sites publish a Merchant Center Feed under an RSS address: ``` ${shop_url}/.well-known/merchant-center/rss.xml ``` **Example:** for [https://shop.fourthwall.com](https://shop.fourthwall.com) the RSS feed URL would be [https://shop.fourthwall.com/.well-known/merchant-center/rss.xml](https://shop.fourthwall.com/.well-known/merchant-center/rss.xml) ## Collections JSON feed A JSON version of the products in a collection. ``` ${shop_url}/collections/{slug}.json ``` **Example:** [https://shop.fourthwall.com/collections/all.json](https://shop.fourthwall.com/collections/all.json) **Paginated:** [https://shop.fourthwall.com/collections/all/2.json](https://shop.fourthwall.com/collections/all/2.json) # Architecture Source: https://docs.fourthwall.com/storefront/architecture How the Storefront API connects your frontend to Fourthwall ## How It All Connects Here's the complete flow from browsing to purchase: ```mermaid theme={null} flowchart TB subgraph Your_Frontend["Your Frontend"] A[Browse Products] B[Manage Cart] end subgraph Storefront_API["Storefront API"] C["/collections/{slug}/products"] D["/products/{slug}"] E["/carts"] end subgraph Fourthwall["Fourthwall"] F[Checkout Page] G[Order Complete] end A -->|"Fetch"| C A -->|"Fetch"| D B -->|"Create/Update"| E B -->|"Redirect with cart_id"| F F -->|"Payment processed"| G G -->|"Return to your site"| Your_Frontend ``` **The journey:** 1. **Browse**: Your frontend fetches products and collections from the Storefront API 2. **Cart**: Customer adds items, you manage the cart via API 3. **Checkout**: Redirect to Fourthwall checkout with the cart ID 4. **Complete**: Fourthwall handles payment, then returns customer to your site ## Deployment Options If you build your storefront on the Fourthwall shop editor, your shop is served directly from our servers to your customers. ```mermaid theme={null} flowchart TB A["Fourthwall.com
shops"] B["Customer"] A -->|"<html>"| B ``` While this is a great way to get started, it does have some limitations. To build a truly custom experience, you can build your own frontend and use the Storefront API to pull in products and handle the cart process. ### With a proxy The most straight-forward way to build a custom storefront is through a proxy server. We've optimized for a setup through Vercel, though other services like Netlify would work as well. ```mermaid theme={null} flowchart TB A["Fourthwall
storefront api"] B["Your site
vercel"] C["Customer"] A -->|"API calls"| B B -->|"API calls"| A B -->|"<html>"| C ``` In this setup, your frontend application will make API calls to the Vercel backend. The Vercel backend acts as a proxy that will then make calls to the Fourthwall API. Your Vercel application can contain whatever code you want, allowing for a fully customizable storefront. ### Separate frontend It is also possible to build your custom site as a static frontend only site. In this setup, the static site will make API calls directly to the Fourthwall API. ```mermaid theme={null} graph LR A["Your site"] B["Fourthwall
storefront api"] C["Customer"] B -->|"API calls"| A A -->|"<html>"| C ``` # Building a Cart & Checkout Source: https://docs.fourthwall.com/storefront/cart-checkout-tutorial End-to-end guide for implementing shopping cart and checkout This guide walks through the complete flow from adding items to a cart through redirecting to checkout. ## Overview ```mermaid theme={null} flowchart LR A[Create Cart] --> B[Add Items] B --> C[Redirect to Checkout] C --> D[Customer Completes Purchase] ``` ## Step 1: Create a Cart First, create an empty cart. You'll get back a `cart_id` that you'll use for all subsequent operations. ```javascript theme={null} const STOREFRONT_TOKEN = "your_storefront_token"; const API_BASE = "https://storefront-api.fourthwall.com/v1"; async function createCart() { const res = await fetch(`${API_BASE}/carts?storefront_token=${STOREFRONT_TOKEN}`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ currency: "USD" }) }); return res.json(); } const cart = await createCart(); console.log(cart.id); // e.g., "22222222-2222-2222-2222-222222222222" ``` Store the `cart_id` in localStorage or a cookie so it persists across page refreshes. ## Step 2: Add Items to Cart Add products to the cart using a variant ID. You can get variant IDs from the [product endpoints](/storefront/products). ```javascript theme={null} async function addToCart(cartId, variantId, quantity = 1) { const res = await fetch( `${API_BASE}/carts/${cartId}/items?storefront_token=${STOREFRONT_TOKEN}`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ variantId, quantity }) } ); return res.json(); } // Add a product to the cart await addToCart(cart.id, "var_xyz789", 2); ``` ## Step 3: View Cart Contents Fetch the current cart to display items to the user: ```javascript theme={null} async function getCart(cartId) { const res = await fetch( `${API_BASE}/carts/${cartId}?storefront_token=${STOREFRONT_TOKEN}` ); return res.json(); } const currentCart = await getCart(cart.id); console.log(currentCart.items); // Array of cart items console.log(currentCart.totals); // Price totals ``` ## Step 4: Redirect to Checkout When the customer is ready to purchase, redirect them to the Fourthwall checkout page using the cart ID: ```javascript theme={null} function redirectToCheckout(cartId, currency = "USD") { const checkoutDomain = "your-shop.fourthwall.com"; // Your checkout domain const params = new URLSearchParams({ cartCurrency: currency, cartId }); const checkoutUrl = `https://${checkoutDomain}/checkout/?${params.toString()}`; window.location.href = checkoutUrl; } // When user clicks "Checkout" button redirectToCheckout(cart.id, "USD"); ``` The checkout URL format is: ``` https://{checkout_domain}/checkout/?cartCurrency={currency}&cartId={cart_id} ``` Make sure to use your shop's checkout domain, not `storefront-api.fourthwall.com`. ### Alternative: Domain cart checkout URL You can also use the domain cart-checkout entrypoint: ```text theme={null} https://{shop_domain}/cart/checkout?cartId={cart_id}¤cy={currency} ``` ### Alternative: Direct product checkout URL If you want to skip cart creation and redirect from variant IDs directly, use: ```text theme={null} https://{shop_domain}/cart/checkout?products={variant_id}:1 ``` For multiple variants: ```text theme={null} https://{shop_domain}/cart/checkout?products={variant_id_1}:1,{variant_id_2}:2 ``` `quantity` is optional and defaults to `1`. For full behavior details, see [Cart Checkout Endpoint](/shop-apis/cart-checkout-endpoint). ## Complete Example Here's a complete implementation you can adapt: ```javascript theme={null} const STOREFRONT_TOKEN = "your_storefront_token"; const API_BASE = "https://storefront-api.fourthwall.com/v1"; const CHECKOUT_DOMAIN = "your-shop.fourthwall.com"; // Cart state let cartId = localStorage.getItem("cartId"); async function ensureCart() { if (cartId) { // Verify cart still exists const res = await fetch( `${API_BASE}/carts/${cartId}?storefront_token=${STOREFRONT_TOKEN}` ); if (res.ok) return cartId; } // Create new cart const res = await fetch(`${API_BASE}/carts?storefront_token=${STOREFRONT_TOKEN}`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ currency: "USD" }) }); const cart = await res.json(); cartId = cart.id; localStorage.setItem("cartId", cartId); return cartId; } async function addToCart(variantId, quantity = 1) { const id = await ensureCart(); const res = await fetch( `${API_BASE}/carts/${id}/items?storefront_token=${STOREFRONT_TOKEN}`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ variantId, quantity }) } ); return res.json(); } function checkout() { if (!cartId) { alert("Your cart is empty"); return; } const params = new URLSearchParams({ cartCurrency: "USD", cartId }); window.location.href = `https://${CHECKOUT_DOMAIN}/checkout/?${params.toString()}`; } ``` ## Next Steps Make your checkout page match your brand See all cart operations # Checkout Setup Source: https://docs.fourthwall.com/storefront/checkout Configure Fourthwall checkout pages for custom storefronts When the user is ready to checkout, you will redirect them to the Fourthwall checkout page. This is where they can review their order and enter their shipping and payment information. Checkout page It's best to style the checkout page so that it matches the rest of your store. This will help create a seamless shopping experience for your customers. We recommend using a simple theme like Clean Frame and then setting the styling (colors + typography) so that it matches your main store. You can [style your checkout process here](https://my-shop.fourthwall.com/admin/dashboard/store-design/layout/index/?redirect). You will also want to connect a custom domain like `checkout..com` to the checkout page so that the redirect from your main store is seamless. You can connect custom domains [here](https://my-shop.fourthwall.com/admin/dashboard/settings/domain/?redirect). In the Vercel starter kit, you can add the checkout domain (custom or otherwise) to `env.local` under `NEXT_PUBLIC_FW_CHECKOUT`. If you are building your own frontend, redirect to: ``` https:///checkout/?cartCurrency=&cartId= ``` Alternative domain entrypoint: ``` https:///cart/checkout?cartId=¤cy= ``` For `/cart/checkout` details (including direct product links like `products=variantId:quantity`), see [Cart Checkout Endpoint](/shop-apis/cart-checkout-endpoint). ## Setting up a meta redirect (recommended) The emails sent from Fourthwall can sometimes include a link to the Fourthwall (checkout) page. When a user clicks on this link, you will want to redirect them to your domain. You can add a redirect script to your `Head` component [here](https://my-shop.fourthwall.com/admin/dashboard/store-design/general/advanced/?redirect). ```html theme={null} ``` # Collection Handles Source: https://docs.fourthwall.com/storefront/collection How to get a collection handle for the Storefront API The Storefront collection APIs use the collection handles (slugs) to identify the collection you want to display. It defaults to the "all" collection, but you can specify a different collection by providing the handle. Looking to fetch products? See [Fetching Products](/storefront/products) for the complete guide. ## The `all` Collection The `all` collection is a special built-in collection that contains **all public products** in your shop. Use it when you want to display your entire catalog: ```javascript theme={null} GET /v1/collections/all/products?storefront_token=YOUR_TOKEN ``` ## Finding Collection Handles Here's how to get the handle for a collection: 1. Go to the [Collections page](https://my-shop.fourthwall.com/admin/dashboard/products/collections/?redirect) 2. Click on the collection you want to get the handle for. Make sure this collection is public. 3. The handle is the last part of the copyable URL. # Getting Started Source: https://docs.fourthwall.com/storefront/getting-started Get started with Storefront API authentication and deployment ## Authentication You can create a Storefront token in your [developer settings](https://my-shop.fourthwall.com/admin/dashboard/settings/for-developers/?redirect). This token will be used to authenticate your requests to the Storefront API. Storefront token ## Vercel The best way to get started is with Vercel and Next.js. Vercel is a cloud platform that makes it easy to deploy websites and applications. Next.js is the React-based framework that Vercel is built on. You can fork our open source project to get started: [![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2FFourthwallHQ%2Fvercel-commerce) Once you have forked the repo, you can follow the instructions in the README to learn how to run the project locally and deploy. The only things you will need to configure are the default environment variables. ## Static page If you decide to build your own frontend, you can simply make fetch requests directly to the Fourthwall API to get the information you need. ```javascript theme={null} const res = await fetch("https://storefront-api.fourthwall.com/v1/collections?storefront_token="); ``` The full set of endpoints available to you can be found in the Storefront API reference. # Storefront API Overview Source: https://docs.fourthwall.com/storefront/overview Build custom storefronts on top of Fourthwall products The Storefront API lets you build custom storefronts on top of your Fourthwall product collections. Create a fully custom shopping experience while leveraging Fourthwall's product catalog, fulfillment, and checkout. ## Quick start Get your storefront token from [Settings > For Developers](https://my-shop.fourthwall.com/admin/dashboard/settings/for-developers/?redirect), then fetch your collections: ```bash theme={null} curl "https://storefront-api.fourthwall.com/v1/collections?storefront_token=YOUR_TOKEN" ``` ## What you can do * **Fetch products and collections** - Display your Fourthwall products on any website or app * **Manage shopping carts** - Create and update carts via API * **Checkout** - Redirect customers to Fourthwall's hosted checkout page ## Next steps Set up authentication and make your first API call Understand how the API connects to your frontend # Fetching Products Source: https://docs.fourthwall.com/storefront/products How to fetch products using the Storefront API The Storefront API organizes products into **collections**. There is no standalone "List Products" endpoint—instead, you fetch products through collections. ## The `all` Collection Every shop has a built-in `all` collection that contains all public products. This is the easiest way to get all products: ```javascript theme={null} // Get all products const res = await fetch( "https://storefront-api.fourthwall.com/v1/collections/all/products?storefront_token=YOUR_TOKEN" ); const { results, paging } = await res.json(); ``` ## Common Patterns ### Get all products (paginated) ```javascript theme={null} async function getAllProducts(token) { const products = []; let page = 0; let hasMore = true; while (hasMore) { const res = await fetch( `https://storefront-api.fourthwall.com/v1/collections/all/products?storefront_token=${token}&page=${page}&size=50` ); const data = await res.json(); products.push(...data.results); hasMore = data.paging.hasNextPage; page++; } return products; } ``` ### Get products from a specific collection ```javascript theme={null} const res = await fetch( "https://storefront-api.fourthwall.com/v1/collections/merch/products?storefront_token=YOUR_TOKEN" ); ``` ### Get a single product by slug Use this when you already know the product slug (e.g., from a URL like `/products/cool-t-shirt`): ```javascript theme={null} const res = await fetch( "https://storefront-api.fourthwall.com/v1/products/cool-t-shirt?storefront_token=YOUR_TOKEN" ); ``` ## Why Collections? Collections let you: * Organize products into categories (e.g., "Apparel", "Accessories") * Control which products appear on your storefront * Create featured or seasonal groupings The `all` collection is always available as a catch-all. # Alerts Source: https://docs.fourthwall.com/streaming/alerts Stream on-screen alerts for purchases and tips by pushing webhook events into an OBS browser source A streaming alert overlay turns shop activity into on-stream engagement. A card animates in when an order or tip lands, the chat sees it, the creator reacts to it live. This guide walks through the architecture for building one against Fourthwall webhooks — modeled on Fourthwall's first-party Alerts app. The overlay itself is a plain webpage your creators drop into OBS, Streamlabs, or any streaming tool that supports HTML browser sources. Webhooks land on your server, and you need to push them out to the OBS browser page in real time. This guide will also show you how to set up an [embedded settings](/apps/embedded-settings) page for your app. This way, the creator can manage their overlay settings without leaving Fourthwall. A complete, runnable implementation of this guide — install, embedded settings, webhook receiver, SSE transport, and overlay — lives in the [streaming-alerts example](https://github.com/FourthwallHQ/fourthwall-examples/tree/main/examples/streaming-alerts) on GitHub. ## Architecture ```mermaid theme={null} sequenceDiagram participant Shop participant Fourthwall participant Server as Your server participant Overlay as OBS browser source Shop->>Fourthwall: ① Order placed / Tip sent Fourthwall->>Server: ② Webhook (ORDER_PLACED / DONATION) Note over Server: ③ Resolve shopId,
fan out to overlays Server-->>Overlay: ④ SSE / WebSocket push Note over Overlay: ⑤ Queue + animate card,
play sound ``` Five moving parts: 1. **Install + webhook registration.** On install, your app runs the OAuth flow once, then subscribes to `ORDER_PLACED` and `DONATION` for that shop, scoped to your webhook receiver URL. 2. **Embedded settings page.** An iframe Fourthwall renders inside the dashboard, identified by a signed handoff. This is the creator's control surface: overlay URL, test alert, and privacy/enable toggles. 3. **Webhook receiver.** A signed HTTP endpoint that validates the payload, looks up the owning shop, and hands the event to the fan-out layer. 4. **Push transport.** A long-lived connection from the browser source to your server. The path-of-least-resistance is Server-Sent Events (SSE) — one direction, automatic reconnect, no protocol upgrade. 5. **Overlay page.** A static HTML/JS page identified by a per-shop slug. It subscribes to the stream, queues incoming events, and animates them one at a time. ## 1. Install the App Use the standard [App OAuth flow](/guides/oauth) so the creator can authorize your app against their shop with one click at install. On callback, exchange the code for an access token and resolve the shop with [`GET /shops/current`](/api-reference/platform/shop/get-current-shop). You need the token just long enough to register the webhooks (next step) — you don't have to store it. Afterward, the [embedded settings](/apps/embedded-settings) handoff re-identifies the shop on every request by signature, so persist only what you key off per shop (the webhook secret, the toggles). ```ts theme={null} // after OAuth callback const shop = await fetch( "https://api.fourthwall.com/open-api/v1.0/shops/current", { headers: { Authorization: `Bearer ${accessToken}` } } ).then(r => r.json()); ``` ## 2. Register the Webhooks Immediately after connecting a shop, register a single webhook subscribed to the alert-worthy events. Subscribing once per shop — instead of per overlay session — keeps the configuration stable across reconnects. ```bash theme={null} POST https://api.fourthwall.com/open-api/v1.0/webhooks Authorization: Bearer {accessToken} Content-Type: application/json { "url": "https://your-app.com/webhooks/fourthwall", "allowedTypes": ["ORDER_PLACED", "DONATION"] } ``` The response includes a `secret` you'll use to [verify signatures](/webhooks/signature-verification) on every incoming event. **Also subscribe to `PLATFORM_APP_DISCONNECTED`.** When a creator disconnects your app from their dashboard, this event lets you clean up their stored tokens and stop trying to push to a stale overlay. ## 3. Receive and Fan Out Your webhook receiver: 1. Verifies the `X-Fourthwall-Hmac-Apps-SHA256` header against the body using the stored `secret`. 2. Resolves the shop from the webhook payload (`shopId` is included). 3. Pushes the event onto the in-memory channel (or pub/sub topic) for that `shopId`. 4. Returns `200 OK` immediately — alert overlays are fire-and-forget; if no overlay is listening, the event is silently dropped. ```ts theme={null} // /api/webhooks/fourthwall export async function POST(req: Request) { const body = await req.text(); const sig = req.headers.get("x-fourthwall-hmac-apps-sha256"); if (!verifySignature(body, sig, secret)) return new Response("bad sig", { status: 401 }); const event = JSON.parse(body); await channel(event.shopId).publish(event); // e.g. Redis pub/sub, in-memory EventEmitter return new Response("ok"); } ``` ## 4. Push to the Overlay The simplest transport that works in every browser source is **Server-Sent Events**. One endpoint per shop, the browser opens an `EventSource`, your server holds the connection open and writes a line of JSON for each event. ```ts theme={null} // /events/:shopId (SSE endpoint) export async function GET(req: Request, { params }: { params: { shopId: string } }) { const stream = new ReadableStream({ start(controller) { const sub = channel(params.shopId).subscribe(event => { controller.enqueue(`data: ${JSON.stringify(event)}\n\n`); }); req.signal.addEventListener("abort", () => sub.unsubscribe()); }, }); return new Response(stream, { headers: { "Content-Type": "text/event-stream", "Cache-Control": "no-cache", "Connection": "keep-alive", }, }); } ``` WebSockets work too, but bring upgrade-handshake complexity for no real gain — alerts are one-way push. ## 5. Build the Overlay Page The creator copies a URL like `https://your-app.com/overlay/{shopId}` and pastes it into OBS as a browser source. The page opens an `EventSource`, queues events, and animates them serially so two cards never overlap. ```html theme={null} ``` A few non-obvious details worth getting right: * **Reconnection.** `EventSource` retries automatically, but back off exponentially on the server side (e.g. `1s × 2^n`) so a flapping creator doesn't hammer you. * **Identifying the shop.** Put the `shopId` in the URL path, not in a query parameter — browser sources sometimes strip query strings on reload. * **No replay.** Don't try to backfill missed events when the overlay reconnects. The creator wasn't on screen to react to them anyway; replaying stale alerts feels worse than dropping them. * **Idempotency.** Webhooks can be retried. Dedupe on the event ID before fanning out so the overlay doesn't show the same alert twice. * **Sound.** Browsers gate audio behind a user gesture, but OBS auto-grants it for browser sources. Test in OBS, not in a regular browser tab. ## 6. The Settings Page The creator manages everything from an [embedded settings](/apps/embedded-settings) page — the iframe Fourthwall renders inside the dashboard. Set its URL under **Settings URL** on your Platform App. Fourthwall loads it with a signed handoff: ``` https://your-app.com/?shop_id={id}&hmac={sig}×tamp={ts} ``` Verify the `hmac` before trusting the request — it's what authenticates the shop, on the page load and on every API call the page makes back to you. It's **HMAC-SHA512, Base64-encoded** (distinct from the SHA256 hex used for webhook delivery) over `timestamp={ts}&shop_id={id}&app_id={app_id}`, signed with the HMAC secret from your Platform App's basic information. ```ts theme={null} import { createHmac, timingSafeEqual } from "node:crypto"; function verifyEmbeddedSettings({ shopId, appId, timestamp, hmac, secret }) { const data = `timestamp=${timestamp}&shop_id=${shopId}&app_id=${appId}`; const expected = createHmac("sha512", secret).update(data).digest("base64"); const a = Buffer.from(expected), b = Buffer.from(hmac); return a.length === b.length && timingSafeEqual(a, b); } ``` Once verified, render the controls: the copyable overlay URL, a **test alert** button that publishes a synthetic event onto the shop's channel, and the privacy/enable toggles below. See the [Embedded Settings](/apps/embedded-settings) guide for the full contract. ## Privacy Supporters can be sensitive about having their real name on a livestream. Give the creator a toggle — on the settings page above — to replace the supporter's name with a generic label ("Anonymous", "A supporter") before the alert is pushed to the overlay. Apply the transformation server-side so the overlay never sees the real name. ## Reference The full implementation of this guide Connect a shop with one click Render your settings page in the dashboard Register subscriptions programmatically Validate the HMAC header Purchase webhook payload Tip webhook payload What happens when your receiver is down # Managing Webhooks via API Source: https://docs.fourthwall.com/webhooks/api-management Programmatically create, update, and manage webhook subscriptions In addition to setting up webhooks through the [dashboard](https://my-shop.fourthwall.com/admin/dashboard/settings/for-developers?redirect), you can programmatically create and manage webhooks using the Platform API. This is useful for: * Automating webhook setup as part of your deployment process * Building integrations that dynamically configure webhooks * Managing webhooks across multiple shops programmatically ## Creating a Webhook Use the [POST /webhooks](/api-reference/platform/webhooks/create-webhook) endpoint to create a new webhook subscription: ```bash theme={null} 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://your-server.com/webhooks/fourthwall", "allowedTypes": ["ORDER_PLACED", "ORDER_UPDATED", "DONATION"] }' ``` The response includes the webhook configuration with its ID and secret key: ```json theme={null} { "id": "wcon_P-VkRfmJTBaC6_Tst22cew", "url": "https://your-server.com/webhooks/fourthwall", "allowedTypes": ["ORDER_PLACED", "ORDER_UPDATED", "DONATION"], "secret": "e3f93c7c-c92b-4b8f-a9b1-5b70e0891abc" } ``` Store the `secret` value securely - you'll need it to [verify webhook signatures](/webhooks/signature-verification). ## Complete Example: Create, Receive, and Verify Here's a complete flow showing how to set up a webhook programmatically and handle incoming events: **Step 1: Create the webhook** ```python theme={null} import requests # Create webhook subscription using Basic Auth response = requests.post( "https://api.fourthwall.com/open-api/v1.0/webhooks", auth=("your_username", "your_password"), json={ "url": "https://your-server.com/webhooks/fourthwall", "allowedTypes": ["ORDER_PLACED"] } ) webhook_config = response.json() webhook_secret = webhook_config["secret"] # Store this securely! ``` **Step 2: Receive and verify webhook events** ```python theme={null} import hmac import hashlib import base64 from flask import Flask, request, jsonify app = Flask(__name__) WEBHOOK_SECRET = "your_stored_secret" def verify_signature(payload, signature_header): """Verify the webhook signature matches.""" digest = hmac.new( WEBHOOK_SECRET.encode('utf-8'), payload, digestmod=hashlib.sha256 ).digest() computed_signature = base64.b64encode(digest).decode('utf-8') return hmac.compare_digest(computed_signature, signature_header) @app.route('/webhooks/fourthwall', methods=['POST']) def handle_webhook(): # Get the signature from headers signature = request.headers.get('X-Fourthwall-Hmac-SHA256') # Verify the signature if not verify_signature(request.data, signature): return jsonify({"error": "Invalid signature"}), 401 # Parse the webhook payload event = request.json # Handle different event types if event["type"] == "ORDER_PLACED": order_data = event["data"] print(f"New order received: {order_data['friendlyId']}") # Process the order... # Always return 200 to acknowledge receipt return jsonify({"status": "received"}), 200 ``` ## API Endpoints The Platform API provides these endpoints for webhook management: | Endpoint | Description | | -------------------------------------------------------------------- | --------------------------------- | | [POST /webhooks](/api-reference/platform/webhooks/create-webhook) | Create a new webhook subscription | | [GET /webhooks](/api-reference/platform/webhooks/list-webhooks) | List all webhooks | | [GET /webhooks/](/api-reference/platform/webhooks/get-webhook) | Get a specific webhook | | [PUT /webhooks/](/api-reference/platform/webhooks/update-webhook) | Update a webhook | | [DELETE /webhooks/](/api-reference/platform/webhooks/delete-webhook) | Delete a webhook | # Webhook Event Types Source: https://docs.fourthwall.com/webhooks/event-types For detailed payload schemas, see the [Webhook Events API reference](/api-reference/order-events/order-placed). # Getting Started with Webhooks Source: https://docs.fourthwall.com/webhooks/getting-started Real-time webhook notifications for shop events Ready to boost your applications, Twitch, or YouTube live stream with Fourthwall's webhooks? This powerful tool helps your apps stay in sync with your shop in real-time, responding to key events as they happen. Picture this: the moment a supporter purchases a product from your shop, a webhook sends a notification your way. Now it's your turn to act, using the data received through your webhook to drive the next steps. ## Example Use Cases Webhooks offer a world of possibilities for real-time application integration: * **Interactive IoT integrations:** Sync your webhooks with smart devices. For instance, set your lights to flash whenever an order is placed. * **Data harvesting for internal usage**: Use webhooks to gather and process data, keeping your internal systems up-to-date. * **External software linkage:** Connect your store with external software, such as accounting tools, to automate and streamline operations. * **Immediate alerts for shipping partners:** Keep your shipping companies in the loop about new orders in real time. * **Automated data clean-up:** Safeguard customer privacy by using webhooks to remove customer data from databases once it's no longer necessary. ## Example Flow ```mermaid theme={null} sequenceDiagram participant Client participant Fourthwall Client->>Fourthwall: ① Create a webhook
subscription for
events Note over Fourthwall: ② Event happened, e.g.
order placed Fourthwall->>Client: ③ Send JSON with
data Client->>Fourthwall: ④ 200 HTTP OK
response ``` 1. A webhook subscription is set up on the 'ORDER\_PLACED' topic for a shop, using a specified HTTPS endpoint (via the [dashboard](https://my-shop.fourthwall.com/admin/dashboard/settings/for-developers?redirect) or the [API](/api-reference/platform/webhooks/create-webhook)). 2. Someone places an order in the shop. 3. Fourthwall takes this order event and sends it, along with the order details (payload), to the subscription endpoint. 4. The client app receives this event, sends back an 'OK 200' HTTP status code, and then can perform any needed actions. ## Next Steps Create and manage webhooks programmatically See all available webhook events and payloads Secure your webhook endpoint Test webhooks during development # Gifting Guide Source: https://docs.fourthwall.com/webhooks/gifting-guide Handle gift purchase webhooks for Twitch gift redemptions This is a beta feature. Below you will find all the information you need to know about gifting with Fourthwall. ## 1. How to enable Streamelements gifting 1. Go to [Twitch Gifting App](https://my-shop.fourthwall.com/admin/dashboard/apps/twitch?redirect) in your Fourthwall admin dashboard 2. Select Streamelements gifting radio button and ensure your shop is connected with Streamelements 3. Click on the `Enable` checkbox to enable the Twitch Gifting App ## 2. Webhook configuration Go to [Webhook Settings](https://my-shop.fourthwall.com/admin/dashboard/settings/for-developers?redirect) and create webhooks for: * `GIFT_DRAW_STARTED` * `GIFT_DRAW_ENDED` ## 3. How to start Gift Draw First, ensure your stream is live: ```bash theme={null} PUT https://api.fourthwall.com/open-api/v1.0/streaming/start Authorization: Basic {credentials} Content-Type: application/json { "services": [ { "type": "TWITCH", "broadcasterId": "test-broadcaster-id", "broadcasterLogin": "raziosx", "thumbnailUrl": "test-thumbnail-url" } ] } ``` Then go to your shop main page and purchase any product as a gift. This will trigger the `Gift draw started` webhook event. ## 4. How to finish Gift Draw Send a request to finish the draw: ```bash theme={null} PUT https://api.fourthwall.com/open-api/v1.0/gifting/draw/{drawId}/finish Authorization: Basic {credentials} Content-Type: application/json { "participants": [ { "service": "TWITCH", "userId": "{twitchWinnerUserId}", "userName": "{twitchWinnerUserName}" } ] } ``` The response will include the winner details along with the redeem URL. The `Gift draw ended` webhook will also be triggered. Gift draws not finished within the configured time (+ 1 minute) will be automatically finished without winners to allow redoing the draw. # Limitations Source: https://docs.fourthwall.com/webhooks/limitations Webhook ordering guarantees, delivery constraints, and best practices ## Key limitations **Ordering is not guaranteed** between different topics for the same resource. For example, an `ORDER_UPDATED` webhook might be delivered before an `ORDER_CREATED` webhook. Your system should handle such cases. * **At-least-once delivery**: Webhook events should be delivered at least once. This means your endpoint might receive the same event more than once. You can detect duplicate events by comparing the `id` value of received events. * **Delivery not guaranteed**: Webhook delivery is not always guaranteed. If you require your data to be always consistent, you should implement reconciliation jobs to fetch data from Fourthwall periodically. # Retry Policies Source: https://docs.fourthwall.com/webhooks/retry-policies Webhook delivery guarantees, retry logic, and timeout handling ## Response policy Your client should respond as quickly as possible with an `OK 200 HTTP` status code. Any other response will result in our system marking the event as failed delivery. Response times longer than **2000ms** may result in a failed delivery. ## Retry policy In case of no response or a response different than OK 200 HTTP status code from your client, our platform will make additional attempts to redeliver the event: * **Initial attempt**: The platform will try to deliver the message 5 times with a timeout set to 5 seconds for each attempt # Signature Verification Source: https://docs.fourthwall.com/webhooks/signature-verification Verify webhook authenticity with HMAC-SHA256 signatures If you want to verify whether the request was in fact sent from Fourthwall you can do that by calculating the webhook digital signature. ## Getting your secret key First head to the [webhook configuration panel](https://my-shop.fourthwall.com/admin/dashboard/settings/for-developers?redirect) in your site settings and find the secret key value assigned to your shop, e.g.: ``` e3f93c7c-c92b-4b8f-a9b1-5b70e0891abc ``` ## Verifying the signature Each webhook request comes with an `X-Fourthwall-Hmac-SHA256` base64 encoded header. To verify the request: 1. Compute the HMAC-SHA256 using your secret key and the entire webhook body 2. Base64 encode the result 3. Compare with the header value ```python theme={null} import hmac import hashlib import base64 SECRET = 'secret_value_from_your_shop_webhook_settings' def verify_signature(data, hmac_header): digest = hmac.new(SECRET.encode('utf-8'), data, digestmod=hashlib.sha256).digest() computed_hmac = base64.b64encode(digest) return hmac.compare_digest(computed_hmac, hmac_header.encode('utf-8')) ``` You can use any programming language as long as the algorithm follows the same principles. ## Signature verification for Platform Apps The verification process for Platform Apps is the same, but uses a different header: `X-Fourthwall-Hmac-Apps-SHA256`. You can get your HMAC key in your app's [settings](https://my-shop.fourthwall.com/admin/dashboard/settings/platform-apps?redirect). # Testing Webhooks Source: https://docs.fourthwall.com/webhooks/testing Test your webhook integration You can test your webhook configuration in the following ways: 1. **Manual testing**: Click the `Send test notification` button on the [webhook list](https://my-shop.fourthwall.com/admin/dashboard/settings/for-developers?redirect). You'll receive an event with test data. You can differentiate test events from normal events by checking if the `testMode` field is set to `true`. 2. **Real events**: Perform the action in your store that you've subscribed to. For example, create an order when you have configured a webhook with `ORDER_PLACED` events. # Webhook Model Source: https://docs.fourthwall.com/webhooks/webhook-model Webhook event structure and fields All webhook events share the same structure. Following is an example JSON event: ```json theme={null} { "testMode": false, "id": "weve_geAva6c1RAuyb9HQxbSlmA", "webhookId": "wcon_P-VkRfmJTBaC6_Tst22cew", "shopId": "sh_7ad0c438-beda-4779-a885-0dc325a755c1", "type": "ORDER_PLACED", "apiVersion": "V1_BETA", "createdAt": "2023-07-12T15:05:11.078089+00:00", "data": { // Event-specific data } } ``` ## Fields Description | Field | Description | | ------------ | ---------------------------------------------------------------------------------------------------------- | | `testMode` | If `true`, indicates the event contains only test data. See [Testing](/webhooks/testing) for more info. | | `id` | Unique identifier of the event. If you receive multiple events with the same id, treat them as duplicates. | | `webhookId` | Indicates which webhook configuration was used. | | `shopId` | Your shop ID, useful if you manage multiple shops under the same webhook endpoint. | | `type` | Indicates which data type you should expect within the event. | | `apiVersion` | Version of the data type that was chosen. | | `createdAt` | Date at which the event was created. | | `data` | Contains all the actual data specific to the type of the event. |