Connect Bubble to 2Checkout (now Verifone) using the API Connector with a per-request HMAC-SHA256 Authorization header computed via a Toolbox plugin JavaScript action before each API call. 2Checkout handles built-in global VAT/GST compliance across 200+ markets, making it the go-to choice for SaaS founders who want to avoid being the EU merchant of record. Backend Workflows (paid plan) receive IPN payment events.
| Fact | Value |
|---|---|
| Tool | 2Checkout (now Verifone) |
| Category | Payments |
| Method | Bubble API Connector |
| Difficulty | Advanced |
| Time required | 3–4 hours |
| Last updated | July 2026 |
2Checkout + Bubble: Global SaaS Billing with Built-In Tax Compliance
2Checkout — rebranded as Verifone in 2021, though most documentation and developer forums still use the 2Checkout name — occupies a specific niche that Stripe and Braintree don't fully cover: it acts as the merchant of record for digital goods sales in 200+ countries, calculating and remitting VAT/GST on your behalf under its 2MONETIZE tier. For Bubble founders building SaaS products or selling software licenses globally, this removes the need to register for EU VAT, Australian GST, or other jurisdiction-specific tax obligations — 2Checkout handles the paperwork.
The Bubble-specific complexity is 2Checkout's HMAC-SHA256 Authorization header. Unlike Stripe's static Bearer token or Braintree's Basic Auth, 2Checkout requires every API request to include an Authorization header in this format: `code="MERCHANT_CODE" date="UNIX_TIMESTAMP" hash="HMAC_SHA256(merchantCode + timestamp, secretKey)"`. The timestamp must be the current Unix time in seconds, which means the header value changes on every request and expires within a few minutes of generation.
Bubble's API Connector cannot compute HMAC hashes or embed dynamic timestamps in headers at call time — it evaluates header values once when you save the configuration. The solution used by Bubble builders is a Before-step workflow pattern: use the Toolbox plugin to run a JavaScript action that computes the HMAC (using CryptoJS, which is available in Bubble's client-side JavaScript context), stores the resulting header string in a custom page state, and then the actual API Connector call reads from that state. Your secret keys never appear in client-visible JavaScript because you pass them as workflow inputs from Private API Connector fields.
Once the auth pattern is established, the 2Checkout API itself is well-structured: order listings support pagination via `offset` and `limit`, the subscriptions endpoint surfaces renewal dates and failed-renewal counts for customer success workflows, and the refund endpoint is a straightforward POST. Backend Workflows (available on Bubble's paid plans) can receive 2Checkout's IPN (Instant Payment Notification) events — sent as `application/x-www-form-urlencoded` POST — to update order status records in real time without polling.
Integration method
REST API calls to https://api.2checkout.com/rest/6.0 with a per-request HMAC-SHA256 Authorization header computed via a Toolbox plugin JavaScript action before each workflow step.
Prerequisites
- A 2Checkout / Verifone merchant account (sign up at 2checkout.com — sandbox accounts are available for testing)
- Merchant Code and Secret Key from the 2Checkout Control Panel → Account → Integrations → API → Secret Word section
- A Bubble app on any plan for basic API Connector setup; a paid Bubble plan (Starter or higher) to use Backend Workflows for IPN events
- The Toolbox plugin installed in your Bubble app (Plugins tab → Add plugins → search 'Toolbox' by Zeroqode — this provides the 'Run Javascript' action needed for HMAC computation)
- Basic familiarity with Bubble workflows, custom states, and the API Connector plugin
Step-by-step guide
Get Your 2Checkout API Credentials and Understand the Auth Model
Log in to the 2Checkout Control Panel at app.2checkout.com. Navigate to Account → Integrations → API. You need two values: your Merchant Code (a short uppercase alphanumeric identifier like 'ABCDE123') and your Secret Word (a long random string — this is your HMAC signing key, sometimes called Secret Key or Secret Word in the UI). Copy both and store them securely. Critically, understand what you're working with before writing a single Bubble configuration: the 2Checkout Authorization header is not a simple Bearer token. It looks like this: `Authorization: code="MERCHANT_CODE" date="1700000000" hash="abc123def..."` The `hash` value is HMAC-SHA256 of the string `MERCHANT_CODE1700000000` (merchant code concatenated directly with the Unix timestamp in seconds), signed with your Secret Word. The timestamp must be within a few minutes of 2Checkout's server clock. This means the entire Authorization header string is different on every request — you cannot save it once in Bubble's API Connector. Also confirm which API version you're using. This guide covers v6.0 (base URL `https://api.2checkout.com/rest/6.0`). If you have an older account that previously used v4.0, endpoint paths and response field names differ — check your 2Checkout Control Panel or contact their support to confirm your account's API version. Finally, identify which pricing tier your account is on: 2SELL (one-time payments), 2SUBSCRIBE (subscriptions), or 2MONETIZE (full tax compliance). Your tier determines which API endpoints are available — the subscriptions and tax endpoints are tier-gated.
Pro tip: The 'Secret Word' in 2Checkout's Control Panel is your HMAC signing key — not a separate API password. Some older 2Checkout documentation calls it 'Secret Key'. Look for it under Account → Integrations → API → Secret Word, not under a generic 'API Keys' section.
Expected result: You have your Merchant Code (short, looks like 'ABCDE123') and Secret Word (long random string) from the 2Checkout Control Panel. You know your API version is 6.0 and your account tier.
Install the Toolbox Plugin and Configure Custom States for Auth
The per-request HMAC computation requires running JavaScript inside a Bubble workflow. The Toolbox plugin by Zeroqode provides the 'Run Javascript' action that makes this possible. Install it first. Go to your Bubble app → Plugins tab → click Add plugins → search for 'Toolbox' → find the result by Zeroqode → click Install. The plugin is free. Next, on the page where you will display 2Checkout data (for example, your Orders page), create a custom state to hold the computed Authorization header string: 1. Click on the page background in the Bubble editor (not on any element) 2. In the right panel, click 'Add a custom state' 3. Name it `auth_header`, type: Text, default value: empty Also create a second custom state: - Name: `unix_timestamp`, type: Number, default value: 0 These states act as a secure relay: the JavaScript action computes the header and writes it to `auth_header`, then the API Connector call reads from `auth_header`. Your Secret Word never appears in the Authorization header stored in Bubble's configuration — only the computed HMAC output does. Note on security: The Secret Word will be passed into the JavaScript action as a workflow input from a Private API Connector field. This means it travels server-side in the API Connector context, not as a visible client-side JavaScript variable hardcoded in the page. This is the correct pattern for Bubble.
Pro tip: If your page already uses the Toolbox plugin for other JavaScript actions, you don't need to install it again — it's a single install per app. Check Plugins tab to see if Toolbox by Zeroqode is already listed.
Expected result: Toolbox plugin is installed in your Bubble app. Your Orders page (or equivalent) has two custom states: `auth_header` (Text) and `unix_timestamp` (Number).
Configure the 2Checkout API Connector with Base URL and Content-Type
Now set up the API Connector. Go to Plugins tab → click API Connector (by Bubble) — if it's not installed, click Add plugins, search 'API Connector', and install it. Inside the API Connector, click 'Add another API'. Name it `2Checkout`. In the Shared headers section, add: - Header key: `Content-Type`, value: `application/json` (leave the Private checkbox unchecked — Content-Type is not sensitive) Do NOT add an Authorization header here as a static shared header — because the auth value changes per request, you will pass it as a call-level header on each individual API call, populated dynamically from the `auth_header` custom state. Now add your first API call. Click 'Add another call'. Name it `Get Orders`. Set: - Use as: Data - Method: GET - URL: `https://api.2checkout.com/rest/6.0/orders/` At the call level (not the shared level), add a header: - Key: `Authorization` - Value: `<auth_header_state>` (you will make this dynamic in workflows — for now, type a placeholder) - Check the **Private** checkbox on this Authorization header Add URL parameters: - `Limit`: `20` (initialize value, mark as 'Private' unchecked — this is a query parameter, not a secret) - `Page`: `1` Before you can initialize the call, you need a valid Authorization header. Use the Toolbox plugin JavaScript action manually once to compute one for initialization: In any test workflow, add a 'Run Javascript' action. Paste this code to compute a test auth header (replace YOUR_MERCHANT_CODE and YOUR_SECRET_WORD with your actual values temporarily for the initialization test only — remove them immediately after): ```javascript var merchantCode = "YOUR_MERCHANT_CODE"; var secretWord = "YOUR_SECRET_WORD"; var timestamp = Math.floor(Date.now() / 1000).toString(); var message = merchantCode + timestamp; var hash = CryptoJS.HmacSHA256(message, secretWord).toString(); var authHeader = 'code="' + merchantCode + '" date="' + timestamp + '" hash="' + hash + '"'; bubble_fn_result(authHeader); ``` Run this action, copy the output, paste it as the Authorization header value in the API Connector for the Initialize Call step. Click Initialize call — you should get a 200 response with an `Items` array and `TotalCount` integer. Bubble auto-detects the response fields. After successful initialization, clear the hardcoded credentials from the JavaScript action — they were only needed for this one-time initialization step.
1{2 "method": "GET",3 "url": "https://api.2checkout.com/rest/6.0/orders/",4 "headers": {5 "Content-Type": "application/json",6 "Authorization": "<auth_header_state> (Private, dynamic from custom state)"7 },8 "params": {9 "Limit": "20",10 "Page": "1"11 }12}Pro tip: The Initialize call in Bubble's API Connector needs a real, successful API response to detect field types. If the Authorization header is expired (timestamp too old), 2Checkout returns a 401. Recompute a fresh auth header for initialization if the first attempt fails.
Expected result: The API Connector 'Get Orders' call is initialized with a detected response structure showing `Items` (list), `TotalCount` (number), and order fields like `OrderNo`, `OrderDate`, `Total`. The call is saved and available in workflows.
Build the 'Compute 2Checkout Auth' Reusable Workflow Action
This is the core of the integration. You need a reusable workflow (or a consistent set of steps you repeat before every 2Checkout API call) that computes the HMAC and stores it in the `auth_header` custom state. Set it up once and reuse it across all pages that call 2Checkout. In the Bubble workflow editor on your Orders page, create a new workflow triggered by whatever action loads data (for example, 'When page is loaded' or a Button click). Before the API Connector call step, add these steps: **Step 1 — Run Javascript (Toolbox plugin)** Action: 'Run Javascript' Javascript to run: ```javascript var merchantCode = bubble_fn_merchantCode(); var secretWord = bubble_fn_secretWord(); var timestamp = Math.floor(Date.now() / 1000).toString(); var message = merchantCode + timestamp; var hash = CryptoJS.HmacSHA256(message, secretWord).toString(); var authHeader = 'code="' + merchantCode + '" date="' + timestamp + '" hash="' + hash + '"'; bubble_fn_result(authHeader); ``` In the Toolbox 'Run Javascript' action, you define input parameters. Map: - `bubble_fn_merchantCode` → your Merchant Code (stored as a Private parameter in the API Connector or in Bubble's app settings) - `bubble_fn_secretWord` → your Secret Word (same Private storage) The action's output (`result`) is the full Authorization header string. **Step 2 — Set State** Element: current page Custom state: `auth_header` Value: Result of Step 1 (the JavaScript output) **Step 3 — API Connector call (Get Orders)** In the Authorization header field for this call step, set the value to: `Current page's auth_header` This three-step pattern (Run JS → Set State → API Call) must be repeated before every 2Checkout API call in every workflow. The timestamp in the HMAC is only valid for a few minutes — do not set auth once on page load and reuse it across multiple delayed actions. Recompute immediately before each call. If you use Backend Workflows to process 2Checkout data, note that Backend Workflows run server-side and CryptoJS is not available in that context. The HMAC computation must happen client-side (in a page workflow) before the server-side API call. Backend Workflows are for receiving inbound IPN events, not for making outbound 2Checkout API calls.
1// Toolbox 'Run Javascript' action content2// Inputs: bubble_fn_merchantCode (Private), bubble_fn_secretWord (Private)34var merchantCode = bubble_fn_merchantCode();5var secretWord = bubble_fn_secretWord();6var timestamp = Math.floor(Date.now() / 1000).toString();7var message = merchantCode + timestamp;8var hash = CryptoJS.HmacSHA256(message, secretWord).toString();9var authHeader = 'code="' + merchantCode + '" date="' + timestamp + '" hash="' + hash + '"';10bubble_fn_result(authHeader);Pro tip: CryptoJS is pre-loaded in Bubble's JavaScript runtime context — you do not need to import or install it separately. If you see a 'CryptoJS is not defined' error in the Toolbox action output, check that the Toolbox plugin version supports the CryptoJS context (current versions as of 2026 do).
Expected result: Running the workflow computes a fresh Authorization header string and stores it in the page's `auth_header` custom state. The Get Orders API Connector call uses this state value and returns a 200 response with order data. You can see the order items logged in Bubble's Logs tab.
Display Orders in a Repeating Group and Build Refund Workflow
With the auth computation working, wire up the orders display and refund actions in your Bubble page. **Orders Repeating Group:** 1. Add a Repeating Group element to your page. Set: - Type of content: 2Checkout Get Orders's Items (Bubble's detected response list type) - Data source: Leave empty for now — you will set it via workflow 2. On the Page Load workflow (or a Load Orders button click), run the three-step auth-then-API pattern and then set the Repeating Group's data to the API call result's `Items` list. 3. Inside the Repeating Group cell, add text elements bound to `Current cell's OrderNo`, `Current cell's OrderDate`, `Current cell's Total`, `Current cell's BillingDetails CustomerName`. 4. Add a Load More button below the Repeating Group. Its workflow: - Step 1: Set State — increment a `page_offset` custom state by 1 - Step 2–4: Compute auth, Set State (auth_header), API call with `Page = Current page's page_offset` - Append the new results to the Repeating Group (use Bubble's 'Make changes to' list merge pattern or a separate display list state) **Refund Workflow:** Add a Refund button inside the Repeating Group cell. Set up a confirmation workflow: 1. Button click → Show a Popup with 'Are you sure you want to refund Order #[OrderNo]?' 2. Popup confirm button workflow: - Step 1 (Run Javascript): compute auth header with HMAC - Step 2 (Set State): `auth_header` = Step 1 result - Step 3 (API Call): Add a new API Connector call named 'Refund Order' - Method: POST - URL: `https://api.2checkout.com/rest/6.0/orders/<OrderNo>/refund/` - Authorization header: `Current page's auth_header` (Private) - Body: `{ "comment": "Refund requested via admin dashboard", "amount": <amount_to_refund> }` - Step 4 (Reload): Trigger the Load Orders workflow to refresh the list Add a custom state `selected_order_no` (Text) that you set when the Refund button is clicked (before the popup opens) so the confirmation popup knows which order to refund. Note on refund eligibility: 2Checkout's refund eligibility varies by payment method and jurisdiction — some methods allow refunds for under 24 hours, others up to 180 days. The API returns an error with message 'Order cannot be refunded' if the window has passed. Display a note in your UI: 'Refund availability depends on payment method and 2Checkout policies — contact support if the refund button is unavailable.'
1{2 "method": "POST",3 "url": "https://api.2checkout.com/rest/6.0/orders/<OrderNo>/refund/",4 "headers": {5 "Content-Type": "application/json",6 "Authorization": "<auth_header_state> (Private, from custom state)"7 },8 "body": {9 "comment": "Refund requested via admin dashboard",10 "amount": "<dynamic: order total or partial amount>"11 }12}Pro tip: 2Checkout's `/orders/{OrderNo}/refund/` endpoint URL requires a trailing slash. Missing the trailing slash returns a 404 that looks like a wrong order number. Always include the trailing slash in the API Connector URL.
Expected result: Your Bubble page shows a paginated list of 2Checkout orders in a Repeating Group. Clicking the Refund button shows a confirmation popup, and confirming it posts to the refund endpoint and refreshes the list. Errors from the API (expired refund window, invalid order) surface in the Bubble workflow logs.
Set Up Backend Workflow for IPN Events (Paid Plan) and Go Live
2Checkout's IPN (Instant Payment Notification) sends a POST request to your Bubble app whenever a payment event occurs — order completed, subscription renewed, chargeback opened, refund processed. This is far more efficient than polling the /orders endpoint because Bubble processes the event in real time with zero Workload Unit polling cost. **Important: Backend Workflows require a paid Bubble plan (Starter or higher). Free plan Bubble apps cannot use this feature.** To set up the IPN Backend Workflow: 1. In Bubble, go to Settings → API → check 'This app exposes a Workflow API' 2. Navigate to Backend Workflows (in the left sidebar under 'Workflows') 3. Click 'Add new endpoint'. Name it `2co_ipn` 4. Click 'Detect request data' — this step waits for a real incoming POST from 2Checkout to auto-detect the field types 5. In your 2Checkout Control Panel, go to Account → Integrations → IPN → IPN Settings 6. Set the IPN URL to: `https://your-app.bubbleapps.io/api/1.1/wf/2co_ipn` 7. In 2Checkout Control Panel, click 'Test IPN' to send a sample POST to your endpoint 8. Back in Bubble, the Detect step should capture the test event and display the detected fields The IPN payload from 2Checkout is sent as `application/x-www-form-urlencoded` (form-encoded), not JSON. The key fields to map are: - `MESSAGE_TYPE`: 'ORDER_CREATED', 'FRAUD_STATUS_CHANGED', 'REFUND_ISSUED', 'SUBSCRIPTION_RENEW', 'CHARGEBACK_INITIATED' - `ORDER_REF`: the 2Checkout order reference number (matches `OrderNo` in the REST API) - `IPN_STATUS`: 'COMPLETE', 'PENDING', 'FRAUD' In the Backend Workflow, add steps: - Find the matching Order Thing in your Bubble database where `OrderRef = Detected data's ORDER_REF` - Make changes to that Thing: set `status = Detected data's IPN_STATUS` - Add conditional branches: if `MESSAGE_TYPE = 'CHARGEBACK_INITIATED'`, send an email to your admin address **Switching to production:** - No base URL change needed — `https://api.2checkout.com` serves both test and live accounts. Test accounts and live accounts are separate accounts with different Merchant Codes and Secret Words. - Update your API Connector's Merchant Code and Secret Word Private fields to your live account values - Update the IPN URL in 2Checkout to use your production Bubble app domain if different - Test a real small transaction end-to-end before going fully live RapidDev's team has configured 2Checkout integrations in Bubble apps with subscription tiers and automated chargeback workflows — if you're building a complex SaaS monetization stack on Bubble, book a free scoping call at rapidevelopers.com/contact.
1// 2Checkout IPN endpoint URL format2// Production: https://your-app.bubbleapps.io/api/1.1/wf/2co_ipn3// Custom domain: https://your-domain.com/api/1.1/wf/2co_ipn45// Sample IPN payload fields (form-encoded):6// MESSAGE_TYPE=ORDER_CREATED7// ORDER_REF=1234567898// IPN_STATUS=COMPLETE9// TOTAL_AMOUNT=29.9910// CURRENCY=USD11// CUSTOMER_EMAIL=customer@example.com12// SUBSCRIPTION_STATUS=active13// NEXT_RENEWAL_DATE=2027-01-10Pro tip: 2Checkout's test IPN sender in the Control Panel sends a real POST to your Bubble endpoint — make sure your Backend Workflow's Detect step is active and waiting before clicking 'Test IPN'. The Detect window in Bubble stays open for a few minutes. If you miss the test POST, click Detect again and resend from 2Checkout.
Expected result: Your Bubble Backend Workflow endpoint at /api/1.1/wf/2co_ipn receives 2Checkout IPN events, detects the form-encoded fields, and updates the corresponding Order Things in your Bubble database. IPN events for chargebacks trigger email notifications to your admin address.
Common use cases
Global SaaS Subscription Billing with Automatic Tax Compliance
Build a Bubble SaaS app where customers subscribe to monthly or annual plans in their local currency. 2Checkout's 2MONETIZE tier automatically calculates EU VAT, Australian GST, and other jurisdiction taxes at checkout, then remits them on your behalf — you never register for foreign tax authorities. Your Bubble app calls the subscriptions API to display renewal dates, plan status, and failed payment counts in a customer dashboard, with Backend Workflow IPN events updating subscription status automatically when renewals succeed or fail.
Show me a page with a Repeating Group listing all active subscriptions from 2Checkout. Each row should display the customer email, plan name, next renewal date, and a 'Cancel' button that calls the cancel subscription endpoint. Add a visual warning badge if NextRenewalAttempts is greater than 0.
Copy this prompt to try it in Bubble
Digital Software License Sales Dashboard
Sell software licenses, ebook bundles, or digital downloads through 2Checkout's 2SELL tier and manage sales from a Bubble admin dashboard. The orders endpoint returns paginated results with buyer details, product names, and payment method information. Build a Repeating Group with a Load More button that increments an offset custom state, and add a per-row refund button that calls the refund endpoint with an Are You Sure? confirmation popup.
Build an order management page that loads the first 20 orders from 2Checkout using the GET /orders endpoint. Include a search bar filtering by buyer email, and a Refund button on each row that opens a confirmation popup before calling POST /orders/{OrderNo}/refund.
Copy this prompt to try it in Bubble
Chargeback and Failed Payment Monitoring
Use a Backend Workflow to receive 2Checkout IPN events in real time, including chargeback notifications, failed subscription renewals, and successful payments. When an IPN arrives, the Backend Workflow creates or updates a Bubble database Thing for that order, sets a status field (Paid, Refunded, Chargeback), and can trigger a notification workflow to alert your team via email or Slack. This replaces polling the orders API and reduces Workload Unit consumption significantly.
Set up a Backend Workflow called 2co_ipn that receives 2Checkout IPN POST events, reads the MESSAGE_TYPE and ORDER_REF fields, finds the matching Order Thing in the Bubble database, and updates its status field. Send an email notification if MESSAGE_TYPE is FRAUD.
Copy this prompt to try it in Bubble
Troubleshooting
401 Unauthorized from 2Checkout API on every request
Cause: The HMAC Authorization header has an expired timestamp (more than a few minutes old) or the hash was computed incorrectly. The most common cause is computing the header once on page load and reusing it for actions taken minutes later.
Solution: Always run the 'Compute 2Checkout Auth' JavaScript action immediately before every API call — not once on page load. Check that the HMAC message string is exactly `merchantCode + timestamp` (no separator, no spaces) and that the timestamp is current Unix seconds (Math.floor(Date.now() / 1000)). Also verify the Merchant Code and Secret Word match the account you're calling (test vs live are separate accounts).
1// Verify HMAC construction in browser console (test only)2var merchantCode = "YOUR_CODE";3var secretWord = "YOUR_SECRET";4var timestamp = Math.floor(Date.now() / 1000).toString();5var message = merchantCode + timestamp; // No separator!6var hash = CryptoJS.HmacSHA256(message, secretWord).toString();7console.log('Authorization: code="' + merchantCode + '" date="' + timestamp + '" hash="' + hash + '"');'There was an issue setting up your call' error when initializing the API Connector
Cause: The Authorization header used during the Initialize Call step has expired. Bubble's Initialize Call sends an actual request to the API, and 2Checkout validates the HMAC timestamp. If more than a few minutes passed between computing the test header and clicking Initialize, the request returns a 401 and Bubble shows this generic error.
Solution: Recompute a fresh HMAC Authorization header immediately before clicking Initialize Call. Use the Toolbox JavaScript action to generate the header value, copy it, paste it into the API Connector Authorization header field, and click Initialize Call within 60 seconds. If initialization succeeds, clear any hardcoded credential values from the JavaScript test.
IPN Backend Workflow receives events but fields show as empty or missing
Cause: 2Checkout IPN sends `application/x-www-form-urlencoded` POST data, not JSON. If the Detect Data step was run without receiving an actual IPN POST (or if it received a JSON-format test instead of a real form-encoded POST), Bubble's detected schema may be empty or mistyped.
Solution: Delete the Backend Workflow endpoint and recreate it. Click 'Detect request data' to start the detection window, then trigger a real test POST from 2Checkout Control Panel → Integrations → IPN → Test IPN. The test sends actual form-encoded data that Bubble's Detect step can parse correctly. Confirm detected fields include MESSAGE_TYPE, ORDER_REF, and IPN_STATUS before building workflow steps.
Refund API call returns 'Order cannot be refunded' error
Cause: The order's refund eligibility window has expired, or the order status is not in a refundable state. 2Checkout refund eligibility varies by payment method and jurisdiction — some methods allow only 24 hours, others up to 180 days.
Solution: Display refund eligibility guidance in the Bubble UI: 'Refund availability depends on payment method — digital goods refunds may only be available within 24 hours of purchase. Contact 2Checkout support for out-of-window refunds.' Add an 'Only when' condition on the refund workflow to check if the order date is within your expected refund window using Bubble's date arithmetic. For orders outside the window, show a disabled state on the Refund button with an explanatory tooltip.
Backend Workflow tab is missing or 'Workflow API is not enabled' appears in API settings
Cause: Backend Workflows (API Workflows) require a paid Bubble plan. Free plan Bubble apps do not have access to this feature. The toggle in Settings → API may be visible but non-functional on the Free plan.
Solution: Upgrade to Bubble's Starter plan or higher to use Backend Workflows for 2Checkout IPN receipt. As a workaround on the Free plan, implement a polling pattern: schedule a recurring 'Schedule API Workflow on a list' to call GET /orders periodically and update Bubble database records. This is less efficient (consumes Workload Units) but functional without a paid plan.
Best practices
- Recompute the HMAC Authorization header immediately before every 2Checkout API call — never store it for reuse across multiple actions or page loads, since the timestamp expires within minutes and any delay causes a 401.
- Store Merchant Code and Secret Word as Private parameters in the API Connector, passed to the Toolbox JavaScript action as workflow inputs — never hardcode them in client-visible JavaScript on the page or in URL parameters.
- Add Privacy rules in Bubble's Data tab for any Things that store 2Checkout order data (customer emails, order amounts, subscription references) to prevent unauthorized read access from client-side queries.
- Use 2Checkout's IPN Backend Workflow instead of polling the /orders API endpoint on a schedule — IPN events are real-time and consume zero Workload Units compared to repeated API Connector polling calls.
- Initialize your API Connector calls with a multi-result test response (an account with multiple orders), not a single-order response — Bubble types single-item responses as objects, and a live multi-order response may mismatch the detected type.
- Monitor Workload Unit consumption in Bubble's Logs tab when first deploying — the HMAC JavaScript action and API Connector call both consume WU. If you add polling fallbacks for the Free plan, cap the polling frequency to avoid unexpected WU overages.
- Clearly label test and live accounts in your API Connector configuration ('2Checkout TEST - Get Orders' vs '2Checkout LIVE - Get Orders') — test and live Merchant Codes and Secret Words are completely different credentials on separate 2Checkout accounts.
- Display 2Checkout's dual branding in your admin UI and any customer-facing text as '2Checkout (Verifone)' — older support tickets, forum posts, and developer docs use the 2Checkout name while newer branding uses Verifone; both terms appear in user searches.
Alternatives
Stripe is the default choice for most Bubble payment integrations: simpler static Bearer token auth (no per-request HMAC), a mature Bubble plugin ecosystem, and straightforward subscription billing. Choose 2Checkout over Stripe when you need automatic merchant-of-record tax compliance for EU VAT, Australian GST, or similar jurisdictions — Stripe Tax exists but you remain the merchant of record and must register in each jurisdiction yourself.
Braintree uses static HTTP Basic Auth against a GraphQL endpoint — much simpler to configure in Bubble's API Connector than 2Checkout's per-request HMAC. Braintree's main advantage over 2Checkout is native PayPal and Venmo support as first-class payment methods. Choose Braintree when PayPal acceptance is a priority; choose 2Checkout when global tax compliance and multi-currency subscription billing across 200+ markets is the requirement.
Payoneer focuses on cross-border B2B payouts and marketplace disbursements rather than customer-facing checkout. Use Payoneer when you need to pay out to sellers, freelancers, or partners across multiple countries. Use 2Checkout when you need to accept customer payments globally with automatic tax compliance — they serve opposite ends of the money flow.
Frequently asked questions
Why does my 2Checkout API call work once but then fail with 401 on the next click?
The HMAC Authorization header includes a Unix timestamp that expires within a few minutes of generation. If you compute it once (on page load, or at the start of a session) and reuse it for later actions, the timestamp becomes stale and 2Checkout rejects the request with 401. The fix is to run the 'Compute 2Checkout Auth' JavaScript action as the first step of every workflow that calls the 2Checkout API — generate a fresh header immediately before each call.
Is the Toolbox plugin required, or is there another way to compute the HMAC in Bubble?
The Toolbox plugin (by Zeroqode) is the most common approach because its 'Run Javascript' action provides access to CryptoJS, which handles HMAC-SHA256. Alternatives include other Bubble plugins that expose JavaScript execution (like 'Javascript to Bubble') or a custom Bubble plugin built specifically for HMAC computation. There is no native Bubble formula function for HMAC — you need JavaScript. The Toolbox plugin is free and widely trusted, making it the recommended choice.
Do I need a paid Bubble plan to use 2Checkout?
No — the API Connector works on all Bubble plans including Free for outbound calls (displaying orders, issuing refunds). However, receiving 2Checkout IPN events via a Backend Workflow endpoint requires a paid plan (Starter or higher). If you're on the Free plan and need payment status updates, you can poll the GET /orders endpoint on a schedule as a workaround, though this consumes more Workload Units and is not real-time.
What's the difference between 2Checkout's 2SELL, 2SUBSCRIBE, and 2MONETIZE tiers, and does it affect the Bubble API?
The tier determines which API endpoints are accessible. 2SELL gives you order creation and management endpoints for one-time payments. 2SUBSCRIBE adds the /subscriptions endpoint for recurring billing, renewal management, and trial handling. 2MONETIZE (the full merchant-of-record tier) adds automatic VAT/GST calculation and remittance — 2Checkout collects and files taxes on your behalf. The API authentication method (HMAC) is the same across all tiers. If a Bubble API call to /subscriptions returns a 403, your account may not include the 2SUBSCRIBE tier.
How do I display 2Checkout order dates in Bubble's date format?
2Checkout returns some timestamps as Unix integers (seconds since epoch). Bubble's formula editor can convert them using `Date(2checkout_OrderDate × 1000)` — multiplying by 1000 converts seconds to milliseconds, which Bubble's Date function understands. Then apply `:formatted as [date format]` to display in your preferred format. Other 2Checkout fields return dates as ISO 8601 strings, which Bubble handles automatically.
Can I use 2Checkout with Bubble's free plan for a live production app?
Yes, but with limitations. The API Connector (outbound calls for orders, refunds, subscription lookups) works on all plans including Free. You can build a functional 2Checkout payment management dashboard on a Free plan. What you cannot do on Free is receive real-time IPN events via Backend Workflows — you'd need to poll for status updates instead. For production SaaS apps where payment confirmation timing matters, upgrading to Starter plan to unlock Backend Workflows is strongly recommended.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation