Connect Bubble to Braintree using the API Connector with HTTP Basic Auth — Public Key as username, Private Key as password, both marked Private. Unlike typical REST APIs, all Braintree operations go to a single `/graphql` endpoint as POST requests with the GraphQL query in the body. Braintree is also the canonical way to accept PayPal and Venmo payments in a Bubble app without redirecting to PayPal's own checkout.
| Fact | Value |
|---|---|
| Tool | Braintree |
| Category | Payments |
| Method | Bubble API Connector |
| Difficulty | Intermediate |
| Time required | 2–3 hours |
| Last updated | July 2026 |
Braintree + Bubble: GraphQL, PayPal/Venmo, and Basic Auth
Braintree's biggest differentiator for Bubble builders is its built-in PayPal and Venmo support — Braintree is PayPal-owned, and when you integrate Braintree into your Bubble app, you get PayPal and Venmo as first-class payment methods alongside cards, without redirecting users to a separate PayPal checkout page.
The API pattern is unusual: instead of different URLs for different operations (like `/transactions`, `/refunds`), all Braintree calls go to a single `/graphql` endpoint as POST requests. The operation is specified via the GraphQL `query` field in the request body. In Bubble's API Connector, you create one call per operation, each pointing to the same endpoint with a different query body.
Authentication uses HTTP Basic Auth with your Public Key as the username and Private Key as the password — both must be marked Private in Bubble's API Connector so they are injected server-side. You also need a `Braintree-Version: 2019-01-01` header on every call — missing this header returns a 400 error.
The Braintree Control Panel provides a GraphQL API Explorer (Tools → GraphQL API Explorer) that lets you test queries against your live or sandbox account before wiring them into Bubble. Use it to validate your query syntax — GraphQL schema errors are harder to read in Bubble's error logs than in a dedicated explorer.
Integration method
Use the built-in API Connector (by Bubble) to call Braintree's GraphQL API at a single `/graphql` endpoint using HTTP Basic Auth (Public Key as username, Private Key as password, both marked Private). Add a `Braintree-Version: 2019-01-01` shared header. Each operation differs only in the `query` and `variables` body fields.
Prerequisites
- A Braintree sandbox or live account. Sign up for free at sandbox.braintreegateway.com — self-serve access is available, unlike Adyen or Worldpay.
- Your Braintree credentials from Control Panel → Settings → API: Merchant ID, Public Key, and Private Key. (Sandbox and live credentials are completely separate.)
- Familiarity with the Bubble API Connector plugin and how to create and initialize API calls.
- A Bubble app on a paid plan (Starter or above) if you need to receive webhook notifications via Backend Workflows.
- For PayPal/Venmo via Drop-in UI: Braintree's Braintree.js client SDK loaded in a Bubble HTML element. This requires knowledge of Bubble's HTML element and the ability to pass data between JavaScript and Bubble workflows using the Toolbox plugin's 'bubble_fn_' pattern.
Step-by-step guide
Step 1: Get Your Braintree Credentials
Log into the Braintree Control Panel. For sandbox testing, use sandbox.braintreegateway.com. For production, use www.braintreegateway.com. Navigate to Settings → API. You will find three credentials: - **Merchant ID**: Your account identifier (not secret, but useful to store) - **Public Key**: Used as the username in HTTP Basic Auth - **Private Key**: Used as the password in HTTP Basic Auth — treat this as a secret Write down all three from both sandbox and production environments. Sandbox credentials will ONLY work against the sandbox base URL (`payments.sandbox.braintreegateway.com`), and production credentials will ONLY work against the live base URL (`payments.braintreegateway.com`). Using either set against the wrong environment returns 401 errors. Braintree also has a useful GraphQL API Explorer in the Control Panel under Tools → GraphQL API Explorer. Before you set up Bubble's API Connector, spend a few minutes in this explorer running sample queries — it validates your query syntax and shows you exactly what the response structure looks like. This saves significant debugging time in Bubble.
1{2 "sandbox": {3 "base_url": "https://payments.sandbox.braintreegateway.com",4 "graphql_endpoint": "/graphql",5 "merchant_id": "your_sandbox_merchant_id",6 "public_key": "your_sandbox_public_key",7 "private_key": "your_sandbox_private_key"8 },9 "live": {10 "base_url": "https://payments.braintreegateway.com",11 "graphql_endpoint": "/graphql",12 "merchant_id": "your_live_merchant_id",13 "public_key": "your_live_public_key",14 "private_key": "your_live_private_key"15 }16}Pro tip: Name your API Connector calls clearly ('Braintree Sandbox - Search Transactions' vs 'Braintree Live - Search Transactions') from the start. Mixing sandbox and live calls in the same unnamed group causes hard-to-track bugs.
Expected result: You have sandbox and live credentials written down, and you have run a test query in Braintree's GraphQL Explorer to confirm your access is working.
Step 2: Configure the API Connector with Basic Auth and Required Headers
Go to Plugins tab → Add plugins → search 'API Connector' (by Bubble) → Install. Open API Connector settings. Click 'Add another API' and name it 'Braintree Sandbox'. Set the base URL to `https://payments.sandbox.braintreegateway.com`. In the Authentication section of the API group, select 'Basic auth'. Enter your Public Key in the Username field and your Private Key in the Password field. Check the 'Private' checkbox for both fields — this marks them as server-side only, ensuring they never appear in browser network requests. In Shared Headers, add three headers: 1. `Braintree-Version: 2019-01-01` — this is MANDATORY; missing it causes 400 or unexpected schema responses 2. `Content-Type: application/json` 3. `Accept: application/json` The `Braintree-Version` header pins you to a stable, well-documented API version. Pin to `2019-01-01` and update it intentionally when Braintree releases a new stable version — do not use `latest` or leave it out.
1{2 "api_name": "Braintree Sandbox",3 "base_url": "https://payments.sandbox.braintreegateway.com",4 "authentication": {5 "type": "basic_auth",6 "username": "<private: your_public_key>",7 "password": "<private: your_private_key>"8 },9 "shared_headers": {10 "Braintree-Version": "2019-01-01",11 "Content-Type": "application/json",12 "Accept": "application/json"13 }14}Pro tip: The `Braintree-Version` header is not optional documentation — it actively controls which response schema you receive. If you set it once and Braintree deprecates that version, calls will start returning schema errors. Check Braintree's changelog periodically.
Expected result: The Braintree Sandbox API group in the API Connector shows Basic Auth configured with Private username/password, and three shared headers including Braintree-Version: 2019-01-01.
Step 3: Add and Initialize the Transaction Search Call
Inside the 'Braintree Sandbox' API group, click 'Add a call'. Name it 'Search Transactions'. Set the method to POST and the endpoint path to `/graphql`. In the body section, select JSON and enter the GraphQL query structure. All Braintree operations use this same format — only the `query` string and `variables` object change: ```json { "query": "query SearchTransactions($input: TransactionSearchInput!, $first: Int) { search { transactions(input: $input, first: $first) { edges { node { id legacyId amount { value currencyCode } status createdAt paymentMethodSnapshot { ... on CreditCardDetails { brandCode last4 } } } } pageInfo { hasNextPage endCursor } } } }", "variables": { "input": { "createdAt": { "greaterThanOrEqualTo": "2026-01-01T00:00:00Z", "lessThanOrEqualTo": "2026-12-31T23:59:59Z" } }, "first": 20 } } ``` Mark `input.createdAt.greaterThanOrEqualTo` and `input.createdAt.lessThanOrEqualTo` as dynamic so the Bubble workflow can filter by date range. Mark `first` as dynamic for pagination control. Click 'Initialize call'. Bubble will run the GraphQL query against your sandbox account. The successful response nests data under `data.search.transactions.edges[].node`. Bubble's type detector will infer the nested structure — verify it correctly identifies the `edges` array and the `node` fields (id, amount, status). Set 'Use as' to 'Data'.
1{2 "query": "query SearchTransactions($input: TransactionSearchInput!, $first: Int) { search { transactions(input: $input, first: $first) { edges { node { id legacyId amount { value currencyCode } status createdAt paymentMethodSnapshot { ... on CreditCardDetails { brandCode last4 } } } } pageInfo { hasNextPage endCursor } } } }",3 "variables": {4 "input": {5 "createdAt": {6 "greaterThanOrEqualTo": "<dynamic: date_from>",7 "lessThanOrEqualTo": "<dynamic: date_to>"8 }9 },10 "first": "<dynamic: page_size>"11 }12}Pro tip: Use Braintree's GraphQL Explorer (Control Panel → Tools → GraphQL API Explorer) to test and refine your query before entering it in Bubble. GraphQL schema errors (wrong field names, missing required arguments) show much clearer error messages in the explorer than in Bubble's Logs tab.
Expected result: Bubble shows 'Search Transactions' with green 'Initialized' status and detected fields including the `edges` array with `node.id`, `node.amount.value`, `node.status`, and `node.paymentMethodSnapshot.last4`.
Step 4: Build the Refund Mutation Call
Add a second call to the 'Braintree Sandbox' API group. Name it 'Refund Transaction'. Method: POST. Endpoint: `/graphql` (same as search — all operations share this path). The refund operation is a GraphQL mutation: ```json { "query": "mutation RefundTransaction($input: RefundTransactionInput!) { refundTransaction(input: $input) { refund { id legacyId amount { value currencyCode } status createdAt } } }", "variables": { "input": { "transactionId": "dHJhbnNhY3Rpb25fYWJjMTIz", "refund": { "amount": "19.99" } } } } ``` Mark `input.transactionId` and `input.refund.amount` as dynamic parameters. Enter test values (a real sandbox transaction ID and an amount) for initialization. In your Bubble page workflow, add the refund action with an 'Only when' condition: `Current cell's Transaction's status is 'SETTLED' or 'SETTLING'`. Braintree only allows refunds on SETTLED or SETTLING transactions — attempting a refund on AUTHORIZED or SUBMITTED_FOR_SETTLEMENT transactions returns a GraphQL error. Build a confirmation dialog before the refund step: use a Bubble Alert or a conditional popup that shows the transaction amount and asks 'Confirm refund of $X?' with a Confirm button triggering the actual refund workflow. This prevents accidental refunds. Set 'Use as' to 'Action'. For RapidDev's clients managing high-volume refund workflows in Bubble with Braintree, we've found that capturing the Braintree transaction `id` (the GraphQL global ID, not `legacyId`) in the Bubble database at transaction time makes mutation calls significantly simpler. Book a free scoping call at rapidevelopers.com/contact if you need help designing your Bubble payment database schema.
1{2 "query": "mutation RefundTransaction($input: RefundTransactionInput!) { refundTransaction(input: $input) { refund { id legacyId amount { value currencyCode } status createdAt } } }",3 "variables": {4 "input": {5 "transactionId": "<dynamic: transaction_global_id>",6 "refund": {7 "amount": "<dynamic: refund_amount_string>"8 }9 }10 }11}Pro tip: Braintree's GraphQL uses global IDs (base64-encoded strings like 'dHJhbnNhY3Rpb24_...' ) for the `transactionId` in mutations, NOT the numeric `legacyId`. Store the GraphQL `id` in your Bubble database at transaction creation time, not the `legacyId`.
Expected result: The 'Refund Transaction' call initializes successfully against a sandbox settled transaction, returning the refund id, amount, and status. The Bubble workflow only triggers the refund when the transaction status is SETTLED or SETTLING.
Step 5: Enable PayPal and Venmo via Braintree Drop-in UI
Braintree's Drop-in UI is a pre-built payment form (rendered by Braintree's JavaScript SDK) that supports cards, PayPal, and Venmo in a single embedded interface. In Bubble, you embed it via an HTML element. Add an HTML element to your Bubble checkout page. In the HTML element editor, paste: ```html <div id="dropin-container"></div> <script src="https://js.braintreegateway.com/web/dropin/1.43.0/js/dropin.min.js"></script> <script> braintree.dropin.create({ authorization: 'YOUR_CLIENT_AUTHORIZATION_TOKEN', container: '#dropin-container', paypal: { flow: 'checkout', amount: '19.99', currency: 'USD' }, venmo: {} }, function(createErr, instance) { if (createErr) { console.log('Drop-in error:', createErr); return; } window.braintreeInstance = instance; }); function submitPayment() { braintreeInstance.requestPaymentMethod(function(err, payload) { if (err) { console.log('Payment method error:', err); return; } bubble_fn_nonce(payload.nonce); }); } </script> ``` The `authorization` value is a Braintree Client Authorization Token — generate one via a separate API Connector call to Braintree's `createClientToken` GraphQL mutation, store it in a page custom state, and pass it to the HTML element via a Bubble dynamic data binding or the Toolbox plugin's 'Set state from JS' mechanism. The `bubble_fn_nonce` function (from the Toolbox plugin) sends the payment method nonce back to Bubble, where a workflow step can receive it and use it in a `chargePaymentMethod` GraphQL mutation to complete the payment. Note: The Drop-in UI only works in the published version of your Bubble app — it may behave differently or not load in Bubble's preview/test mode due to domain restrictions.
1<!-- Bubble HTML element for Braintree Drop-in UI -->2<div id="dropin-container"></div>3<script src="https://js.braintreegateway.com/web/dropin/1.43.0/js/dropin.min.js"></script>4<script>5 // 'authorization' is a Client Authorization Token from a Bubble custom state6 // Replace YOUR_CLIENT_TOKEN with a Bubble dynamic expression or use Toolbox to inject it7 braintree.dropin.create({8 authorization: 'YOUR_CLIENT_TOKEN',9 container: '#dropin-container',10 paypal: {11 flow: 'checkout',12 amount: '19.99',13 currency: 'USD'14 },15 venmo: {}16 }, function(createErr, instance) {17 if (createErr) { console.error('Drop-in error:', createErr); return; }18 window.braintreeInstance = instance;19 });2021 // Call this from a Bubble workflow via Toolbox plugin's Run JavaScript action22 function submitPayment() {23 if (!window.braintreeInstance) { return; }24 braintreeInstance.requestPaymentMethod(function(err, payload) {25 if (err) { console.error('Payment error:', err); return; }26 // Send nonce back to Bubble via Toolbox plugin27 bubble_fn_nonce(payload.nonce);28 });29 }30</script>Pro tip: Client Authorization Tokens are short-lived (valid for a limited time). Generate a new token for each page load by calling Braintree's `createClientToken` GraphQL mutation in a 'Page is loaded' workflow and storing the result in a custom state. Do not hardcode a token.
Expected result: The Braintree Drop-in UI renders in the Bubble HTML element showing card entry fields, PayPal, and Venmo buttons. Selecting a payment method and clicking submit sends the nonce to Bubble via the Toolbox plugin.
Step 6: Set Up Webhooks and Go Live
Braintree sends webhook notifications for subscription events (subscription charged, subscription expired), dispute events (opened, won, lost), and payment lifecycle events. Setting up a Backend Workflow to receive them requires a paid Bubble plan. Braintree webhooks use a non-standard format: the payload is Base64-encoded XML, not JSON. This makes parsing in a Bubble Backend Workflow more complex than typical JSON webhooks. For simpler setups at early stage, consider polling instead of webhooks: use a 'Recurring event' Backend Workflow (paid plan) that runs every hour, calls Braintree's GraphQL to fetch recent transactions with status SUBMITTED_FOR_SETTLEMENT, and updates Bubble DB records accordingly. If you do need webhook support: 1. Settings → API → check 'This app exposes a Workflow API'. 2. Backend Workflows → new workflow named `braintree_webhook`. 3. Register the URL in Braintree Control Panel → Settings → Webhooks: `https://yourapp.bubbleapps.io/api/1.1/wf/braintree_webhook`. 4. The webhook body contains a Base64-encoded XML string in the `bt_payload` parameter. Decoding and parsing XML in Bubble requires a Custom Action using `atob()` and an XML parser in JavaScript — use the Toolbox plugin for this. 5. After decoding, extract the `kind` field (event type) and `subject` fields to determine what action to take. To go live: - Add a second API Connector group named 'Braintree Live' with base URL `https://payments.braintreegateway.com`, your live Public Key and Private Key in Basic Auth. Never mix sandbox and live keys in the same API Connector group.
1// Braintree webhook payload structure2// Content-Type: application/x-www-form-urlencoded3// Fields:4// bt_signature: webhook signature for verification5// bt_payload: Base64-encoded XML string67// After decoding bt_payload with atob():8// <notification>9// <kind>subscription_charged_successfully</kind>10// <timestamp>2026-01-15T12:00:00Z</timestamp>11// <subject>12// <subscription>13// <id>sub_abc123</id>14// <plan-id>monthly_pro</plan-id>15// <status>Active</status>16// <next-billing-date>2026-02-15</next-billing-date>17// <transactions>18// <transaction>19// <id>txn_xyz789</id>20// <amount>29.99</amount>21// <status>settled</status>22// </transaction>23// </transactions>24// </subscription>25// </subject>26// </notification>2728// Webhook kinds (common):29// subscription_charged_successfully30// subscription_charged_unsuccessfully31// subscription_expired32// dispute_opened33// dispute_won34// dispute_lost35// check (sent as verification when webhook is first registered)Pro tip: When Braintree registers a new webhook, it sends a 'check' event to verify the endpoint is reachable. Your Backend Workflow should respond to this with a 200 status regardless of payload content — Bubble does this by default.
Expected result: Your Bubble Backend Workflow receives Braintree webhook events, the bt_payload is decoded from Base64, the event kind is extracted, and your workflow updates relevant Bubble database records (subscriptions, transactions) accordingly. Live credentials are active and processing real payments.
Common use cases
PayPal and Venmo Payment Acceptance
Bubble apps that need to accept PayPal or Venmo alongside credit cards should use Braintree rather than integrating PayPal's API directly. Braintree's Drop-in UI, embedded via an HTML element in Bubble, presents all payment methods in one interface — the user picks cards, PayPal, or Venmo, and Braintree handles the OAuth flow.
Add a Braintree Drop-in UI to my Bubble checkout page via an HTML element that shows credit card, PayPal, and Venmo options. When the user submits, capture the payment method nonce and pass it to a Bubble workflow that calls Braintree's GraphQL chargePaymentMethod mutation.
Copy this prompt to try it in Bubble
Transaction Search and Dispute Management Dashboard
Build a Bubble back-office for payment teams to search transactions by date range, view settlement status, and see open disputes. Braintree's GraphQL `search` query supports flexible filtering by amount range, created date, and status — bind results to a Repeating Group for inline refund and dispute actions.
Build a Bubble dashboard that queries Braintree's SearchTransactions GraphQL operation with date range filters, displays results in a Repeating Group with columns for amount, status, and payment method, and allows admins to trigger a refund mutation with a confirmation dialog.
Copy this prompt to try it in Bubble
Subscription Billing Management
Braintree's subscription engine (Plans, Subscriptions) allows Bubble apps to sell recurring products. Use Braintree's GraphQL API to list active subscriptions, check next billing dates, and cancel subscriptions — triggering Bubble database updates and email notifications via a Backend Workflow listening for subscription lifecycle webhooks.
Build a Bubble customer portal that shows each user's Braintree subscription status, next billing date, and plan name. Include a Cancel Subscription button that calls Braintree's cancelSubscription GraphQL mutation and updates the Bubble user record to subscription_status: cancelled.
Copy this prompt to try it in Bubble
Troubleshooting
API Connector Initialize call returns 'There was an issue setting up your call' or a 401 Unauthorized error
Cause: HTTP Basic Auth is misconfigured — either the Public Key (username) or Private Key (password) is wrong, or you are using sandbox credentials against the live base URL (or vice versa). The Braintree-Version header may also be missing.
Solution: Open Plugins → API Connector → Braintree Sandbox. Verify the Authentication section shows 'Basic auth' with your Public Key in Username and Private Key in Password, both Private. Confirm the shared headers include `Braintree-Version: 2019-01-01`. Check that the base URL is the sandbox URL when using sandbox credentials. Open Bubble's Logs tab after the failed initialize to see the raw HTTP response code from Braintree.
GraphQL query returns errors about unknown fields or missing required arguments
Cause: The GraphQL query string has a field name typo, an incorrect type name, or a missing required argument. Braintree's GraphQL schema is strict — any deviation from the schema returns a 200 response with an `errors` array rather than a 4xx status, so Bubble may show the call as 'successful' but with no data.
Solution: Paste your GraphQL query into Braintree's GraphQL Explorer (Control Panel → Tools → GraphQL API Explorer) and run it there first. The Explorer provides line-by-line schema validation and auto-complete. Copy the validated query into your Bubble API Connector call body. Note that Braintree wraps errors in `data.errors[]` with `message` and `type` fields — check these in Bubble's response inspector.
Refund mutation fails with 'Transaction must be in a settled or settling state'
Cause: The transaction is still in AUTHORIZED or SUBMITTED_FOR_SETTLEMENT status — Braintree settlement typically takes until end of the business day. Refunding before settlement is not possible via the refund mutation; you would need to void the transaction instead.
Solution: Add an 'Only when' condition in your Bubble refund workflow: check that the transaction status stored in your Bubble database is 'SETTLED' or 'SETTLING'. For freshly authorized transactions, show a message 'Refund available after settlement (typically end of day)' and offer a Void option instead (using the `reverseTransaction` mutation).
The Braintree Drop-in UI does not load in the Bubble HTML element, or PayPal button does not appear
Cause: The Braintree.js SDK version in the script tag may be outdated, the `authorization` (client token) may be expired or missing, or the HTML element's domain is not listed in Braintree's allowed domains. The Drop-in UI also behaves differently in Bubble's editor preview vs the published app.
Solution: First, test the Drop-in in your published Bubble app URL (not the editor preview). Check Bubble's browser console for JavaScript errors — common causes: `authorization` is undefined (client token not passed correctly), or 'client-side request must originate from a non-localhost domain'. Update the Braintree.js version in the script src tag to the latest stable version. Ensure your Bubble app's domain is listed in Braintree's Control Panel under Settings → Allowed Domains.
Backend Workflow never receives Braintree webhook events even though the endpoint URL looks correct
Cause: The Bubble app may be on the Free plan (Backend Workflows require paid plans), the Workflow API may not be enabled in Settings, or the registered webhook URL includes '/initialize' which is only for Bubble's detection step.
Solution: Go to Settings → API and confirm 'This app exposes a Workflow API' is checked, and that your plan is Starter or above. In Braintree's Control Panel → Settings → Webhooks, confirm the registered URL is your production Backend Workflow URL without '/initialize'. Send a test webhook from Braintree's webhook settings page and watch Bubble's Logs tab for the incoming request.
Best practices
- Always store Braintree's GraphQL global transaction ID (the `id` field, not `legacyId`) in your Bubble database at transaction time. GraphQL mutations like `refundTransaction` require the global ID — using `legacyId` in mutations will cause schema errors.
- Mark both Public Key (username) and Private Key (password) as Private in Bubble's API Connector Basic Auth configuration. Private Keys must never appear in browser network requests — Bubble's Private flag ensures they are handled server-side.
- Use Braintree's GraphQL Explorer (Control Panel → Tools → GraphQL API Explorer) to validate every query and mutation before entering it in Bubble. GraphQL errors return HTTP 200 with an `errors` array, not a 4xx code, so they can be mistaken for successes in Bubble's logs.
- Set the `Braintree-Version: 2019-01-01` header on every call and update it intentionally — never omit it. Missing this header causes 400 errors or unexpected response schemas.
- Maintain completely separate API Connector groups for sandbox ('Braintree Sandbox') and live ('Braintree Live') with their respective base URLs and credentials. Never mix sandbox and live credentials in the same group.
- Add Bubble Data tab → Privacy rules for any Transaction or Customer type you create from Braintree data. Without privacy rules, Bubble allows all authenticated (and sometimes unauthenticated) users to read all records.
- For early-stage apps where webhook complexity is a barrier, use scheduled Bubble Backend Workflows (paid plan) to poll Braintree's GraphQL for settlement status updates hourly — simpler to implement than parsing Braintree's Base64 XML webhook format.
- For subscription billing, listen to Braintree's `subscription_charged_unsuccessfully` webhook event and build a dunning flow in Bubble — alert the customer, flag the subscription in your Bubble database, and trigger retry logic after failed payments.
Alternatives
Stripe uses a standard REST API with different URL paths per operation and a Bearer token header — a more familiar pattern for Bubble builders than Braintree's single-endpoint GraphQL. Stripe has a Bubble marketplace plugin. Choose Braintree specifically when PayPal or Venmo acceptance is a business requirement, or when you prefer no monthly fees at lower volumes.
PayPal Payouts is for sending money out (mass payouts to sellers, contractors). Braintree is for accepting payments in (cards, PayPal, Venmo from customers). If you need both accepting and paying out with PayPal, use Braintree for inbound and PayPal Payouts for outbound — both can coexist in the same Bubble app.
Adyen is enterprise-tier with volume minimums and no self-serve onboarding. Braintree offers self-serve sandbox access and no monthly fee, making it suitable for smaller Bubble apps. Choose Braintree for PayPal/Venmo integration; consider Adyen when your business volume qualifies for interchange-plus pricing.
Frequently asked questions
Why does Braintree use a single /graphql endpoint for everything instead of different URLs?
Braintree's API is a GraphQL API, not a traditional REST API. In GraphQL, all operations (queries and mutations) go to a single endpoint, and the operation type is specified in the request body via the `query` field. In Bubble's API Connector, this means you create one call per operation (search, refund, etc.), each pointing to the same `/graphql` path but with different body content.
Why do I need the Braintree-Version header and what happens if I omit it?
The `Braintree-Version: 2019-01-01` header tells Braintree which version of the GraphQL schema you expect in responses. Omitting it causes a 400 Bad Request error or an unexpected response schema. Always include this header in the API Connector's shared headers, and update it intentionally when Braintree releases a new stable version.
Can I use Braintree on the Bubble Free plan?
Yes, you can use the API Connector calls (search transactions, process refunds, generate client tokens) on the Free plan. However, receiving Braintree webhook notifications for subscription events or disputes requires Backend Workflows, which are only available on paid Bubble plans (Starter+). For early-stage apps, consider polling for status updates instead.
Why does Braintree send webhook payloads as Base64-encoded XML instead of JSON?
This is a legacy design choice in Braintree's webhook system. The `bt_payload` field contains a Base64-encoded XML string. In Bubble's Backend Workflow, you need to decode it using `atob()` via a JavaScript Custom Action (Toolbox plugin) and then parse the XML to extract event data. For simpler subscription status tracking, polling Braintree's GraphQL API on a schedule is often easier than setting up XML parsing.
What is the difference between Braintree's `id` and `legacyId` in transaction responses?
Braintree's GraphQL API returns two IDs for each transaction: `id` is the GraphQL global ID (a base64-encoded string used in GraphQL mutations like `refundTransaction`), and `legacyId` is the traditional numeric transaction ID used in Braintree's older REST API and Control Panel URLs. Always store and use the GraphQL `id` for Bubble workflows that call mutations.
Does Braintree work with Bubble's sandbox/test mode?
Braintree has its own sandbox environment at payments.sandbox.braintreegateway.com with separate sandbox credentials. This is not the same as Bubble's preview mode — Bubble's preview URL is just a different domain, not a sandboxed API. Use Braintree sandbox credentials with Braintree's sandbox base URL during development, regardless of whether you are in Bubble's editor or published preview.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation