# How to Integrate Retool with Braintree

- Tool: Retool
- Difficulty: Intermediate
- Time required: 20 minutes
- Last updated: April 2026

## TL;DR

Connect Retool to Braintree using a REST API Resource with Basic Auth — your Braintree Public Key as the username and Private Key as the password. Query Braintree's GraphQL API (preferred) or REST endpoints to search transactions, manage customers, view disputes, and process refunds from a Retool payment operations panel built for PayPal and Venmo-integrated businesses.

## Why Build a Retool Braintree Dashboard?

Businesses processing payments through Braintree — particularly those using PayPal and Venmo payment methods alongside credit cards — need an internal operations panel that gives billing support agents and payment operations teams direct access to transaction data without requiring full Braintree Control Panel access. A Retool Braintree dashboard enables support agents to look up a customer's payment history, review a specific transaction's status, issue refunds, and check dispute status — all in one focused interface rather than navigating Braintree's multi-section control panel.

Braintree is the payment infrastructure behind many marketplace, subscription, and e-commerce businesses that integrate PayPal as a first-class payment method. These businesses often have high transaction volumes where manual lookups in the Braintree Control Panel are inefficient. A Retool dashboard can combine Braintree transaction data with your internal customer database, CRM records, and order management system to give support agents complete context — matching a Braintree transaction ID to an internal order, then seeing the customer's full history alongside the payment details.

Braintree's modern GraphQL API provides flexible querying — you can fetch exactly the transaction fields you need, filter by multiple criteria in a single request, and traverse relationships between customers, payment methods, and transactions in one query. Retool's JavaScript transformer handles GraphQL responses naturally since they return as standard JSON. The server-side proxying model means your Braintree Private Key never appears in browser network logs, which is essential for a payment platform where Private Key exposure would allow unauthorized refunds and customer data access.

## Before you start

- A Retool account (Cloud or self-hosted) with permission to add Resources
- A Braintree account — both sandbox (free) and production accounts are available; use sandbox for development and testing
- Your Braintree API credentials: Merchant ID, Public Key, and Private Key — found in Braintree Control Panel → Settings → API → API Keys
- Understanding of whether you'll use Braintree's GraphQL API (preferred, more flexible) or the older REST/XML-based API (for legacy operations)
- For the sandbox environment: test credit card numbers from Braintree's documentation to test transaction queries

## Step-by-step guide

### 1. Locate Braintree API credentials and configure the Resource

Braintree uses a three-part credential system: Merchant ID (identifies your account), Public Key (username for API authentication), and Private Key (password for API authentication). Navigate to the Braintree Control Panel (sandbox.braintreegateway.com for sandbox, www.braintreegateway.com for production).

Go to Settings → API → API Keys. You'll see your Merchant ID displayed at the top. Click Generate New API Key if you haven't already, or use an existing key. The table shows your Public Keys — click 'View' next to a public key to see the corresponding Private Key. Copy both the Public Key (visible) and Private Key (the associated secret).

Also note your Merchant ID for use in API request headers.

Open Retool → Resources tab → Add Resource → REST API:
- Name: 'Braintree API'
- Base URL: https://payments.sandbox.braintreegateway.com (for sandbox) or https://payments.braintreegateway.com (for production)
- Authentication: Basic Auth
- Username: your Braintree Public Key
- Password: your Braintree Private Key

Add default headers:
- Braintree-Version: 2019-01-01 (for GraphQL API version pinning)
- Content-Type: application/json
- Accept: application/json

Store your Merchant ID as a Retool configuration variable (Settings → Configuration Variables → BRAINTREE_MERCHANT_ID) since it's needed in request paths and query variables. Store Public Key and Private Key as additional configuration variables referenced in the resource's Basic Auth fields.

Click Save Changes and Test Connection.

**Expected result:** The Braintree API resource appears in the Resources list. Test Connection returns a successful response from Braintree's API base URL confirming the Public Key and Private Key credentials are valid.

### 2. Query transactions using Braintree's GraphQL API

Braintree's GraphQL API endpoint is at /graphql — all GraphQL queries are POST requests to this single endpoint with the query in the request body. This is Braintree's preferred modern API.

Create a transaction search query in your Retool app:
- Method: POST
- Path: /graphql
- Body type: JSON
- Body:
```json
{
  "query": "query SearchTransactions($input: TransactionSearchInput!) { search { transactions(input: $input) { edges { node { id amount { value currencyIsoCode } status createdAt merchantAccountId customer { email } paymentMethodSnapshot { ... on CreditCardDetails { brandCode last4 expirationMonth expirationYear } ... on PayPalTransactionDetails { payerEmail } } } } } } }",
  "variables": {
    "input": {
      "status": { "in": ["SETTLED", "SUBMITTED_FOR_SETTLEMENT", "AUTHORIZED"] },
      "createdAt": {
        "greaterThanOrEqualTo": "{{ dateRange.start || new Date(Date.now() - 30*24*60*60*1000).toISOString() }}",
        "lessThanOrEqualTo": "{{ dateRange.end || new Date().toISOString() }}"
      }
    }
  }
}
```

The GraphQL response wraps results in data.search.transactions.edges where each item has a node property containing the transaction fields. Add a JavaScript transformer to flatten the edges into clean table rows.

For customer email filtering, add a customerDetails search condition to the variables input when the email filter has a value.

```
// Transformer for Braintree GraphQL transaction search response
// data.search.transactions.edges is the array of { node: transaction } objects
const edges = data?.search?.transactions?.edges || [];
return edges.map(edge => {
  const tx = edge.node;
  const paymentSnapshot = tx.paymentMethodSnapshot || {};
  let paymentMethod = 'Unknown';
  let paymentDetail = '';
  if (paymentSnapshot.brandCode) {
    paymentMethod = paymentSnapshot.brandCode;
    paymentDetail = `...${paymentSnapshot.last4 || ''}`;
  } else if (paymentSnapshot.payerEmail) {
    paymentMethod = 'PayPal';
    paymentDetail = paymentSnapshot.payerEmail;
  }
  return {
    id: tx.id,
    amount: tx.amount ? `${tx.amount.value} ${tx.amount.currencyIsoCode}` : '',
    amount_numeric: parseFloat(tx.amount?.value || 0),
    status: tx.status || '',
    customer_email: tx.customer?.email || '',
    payment_method: paymentMethod,
    payment_detail: paymentDetail,
    merchant_account: tx.merchantAccountId || '',
    created_at: tx.createdAt
      ? new Date(tx.createdAt).toLocaleString()
      : ''
  };
});
```

**Expected result:** A table populates with Braintree transactions showing amount, status, payment method, and customer email. Date range controls filter the results. The transformer cleanly separates credit card and PayPal payment method details.

### 3. Add transaction detail view and refund capability

When an agent selects a transaction row, load a full detail panel with additional transaction metadata. Create a transaction detail query:
- Method: POST
- Path: /graphql
- Body with a transaction(id: "...") query that fetches extended fields:
  - billing address, shipping address
  - risk data (fraudFiltered, decisionReasonCodes)
  - status history (for tracking authorization → settlement flow)
  - processor response code and text
  - refundedTransactionId (if this was a refund)

For refund processing, create a refund mutation query:
- Method: POST
- Path: /graphql
- Body:
```json
{
  "query": "mutation RefundTransaction($input: RefundTransactionInput!) { refundTransaction(input: $input) { refund { id amount { value } status } } }",
  "variables": {
    "input": {
      "transactionId": "{{ transactionsTable.selectedRow.id }}",
      "amount": "{{ refundAmountInput.value || transactionsTable.selectedRow.amount_numeric }}"
    }
  }
}
```

Set the refund query to Manual trigger. Add event handlers: On Success → refresh the transactions query, show a notification 'Refund issued: {{ refundQuery.data.refundTransaction.refund.id }}', and log the refund to an internal audit_log table with the agent email, transaction ID, refund amount, and timestamp.

Drag a Number Input component for refund amount (default value: {{ transactionsTable.selectedRow.amount_numeric }}) and a Button with Confirm before running enabled. Display refund details in a success notification.

```
// GraphQL mutation body for Braintree refund — use as JSON body
// Configured as a JavaScript expression in the query body field
{
  "query": "mutation RefundTransaction($input: RefundTransactionInput!) { refundTransaction(input: $input) { refund { id amount { value currencyIsoCode } status createdAt } } }",
  "variables": {
    "input": {
      "transactionId": "{{ transactionsTable.selectedRow.id }}",
      "amount": "{{ refundAmountInput.value ? String(refundAmountInput.value) : undefined }}"
    }
  }
}
```

**Expected result:** Selecting a transaction row loads a detail panel with billing/shipping address, processor response, and risk data. The refund panel shows the transaction amount defaulting to a full refund. After refund confirmation, the transaction table refreshes and the new REFUNDED status appears.

### 4. Add dispute management and customer search panels

Create a dispute search query using Braintree's GraphQL API:
- Method: POST
- Path: /graphql
- Body with disputes search query filtering by status (OPEN, WON, LOST, EXPIRED) and sorting by receivedDate

The disputes response includes: id, amount, reason, status, receivedDate, replyByDate, transaction.id, and merchantAccountId. Create a transformer that calculates days remaining until reply deadline.

For customer search, use Braintree's legacy REST API (the GraphQL customers API is limited):
- Method: POST
- Path: /merchants/{{ retoolContext.configVars.BRAINTREE_MERCHANT_ID }}/customers/advanced_search_ids
- Body: XML-formatted search criteria (Braintree's search uses XML even for JSON REST endpoints)

Alternatively, use the GraphQL customer search:
```json
{
  "query": "{ search { customers(input: { email: { is: \"{{ customerEmailInput.value }}\" } }) { edges { node { id firstName lastName email createdAt defaultPaymentMethod { id usage } } } } } }"
}
```

Create two additional tabs:
1. Disputes tab — dispute table sorted by reply-by date with urgency color coding
2. Customers tab — customer search by email with vaulted payment methods and transaction history

For complex Braintree integrations involving custom merchant accounts, subscription management, and multi-currency dispute workflows, RapidDev's team can help architect and build your Retool solution.

```
// Transformer for Braintree GraphQL disputes response
// Calculates days remaining and formats dispute data for the table
const edges = data?.search?.disputes?.edges || [];
return edges.map(edge => {
  const dispute = edge.node;
  const replyBy = dispute.replyByDate ? new Date(dispute.replyByDate) : null;
  const today = new Date();
  const daysRemaining = replyBy
    ? Math.ceil((replyBy - today) / (1000 * 60 * 60 * 24))
    : null;
  let urgency = 'normal';
  if (daysRemaining !== null) {
    if (daysRemaining <= 3) urgency = 'critical';
    else if (daysRemaining <= 7) urgency = 'warning';
    else urgency = 'ok';
  }
  return {
    id: dispute.id,
    amount: dispute.amountDisputed
      ? `${parseFloat(dispute.amountDisputed).toFixed(2)} ${dispute.currencyIsoCode || 'USD'}`
      : '',
    reason: dispute.reason || '',
    status: dispute.status || '',
    received_date: dispute.receivedDate
      ? new Date(dispute.receivedDate).toLocaleDateString()
      : '',
    reply_by_date: replyBy ? replyBy.toLocaleDateString() : '',
    days_remaining: daysRemaining !== null ? daysRemaining : 'N/A',
    urgency,
    transaction_id: dispute.transaction?.id || '',
    merchant_account: dispute.merchantAccountId || ''
  };
});
```

**Expected result:** A disputes table shows open disputes sorted by urgency with color-coded deadline indicators. A customer search panel lets agents look up customers by email and view their vaulted payment methods. The dashboard provides complete Braintree operations coverage in three tabs.

### 5. Build a subscription management view and daily payments summary

Braintree's subscription management allows viewing and modifying recurring billing plans. Create a subscription search query:
- Method: POST  
- Path: /graphql
- Body with subscription GraphQL query filtering by status (ACTIVE, PAST_DUE, CANCELED) and customer ID from the customer search panel

The subscription response includes: id, planId, status, price, currentBillingCycle, numberOfBillingCycles, nextBillingDate, and paymentMethodToken.

Create a daily summary view at the top of the dashboard using Statistics components:
- Total settled amount today: sum of amount_numeric from transactions query filtered to today's date and SETTLED status
- Transaction count today: count of today's transactions
- Open disputes: count from disputes query filtered to OPEN status
- Refunds issued today: count of REFUNDED transactions created today

For cancelling a subscription (operations teams sometimes need this for churned customers):
- Method: POST
- Path: /graphql
- Body: cancelSubscription mutation with the subscription ID from the selected row

Add retry functionality for past-due subscriptions:
- Method: POST
- Path: /graphql  
- Body: retrySubscriptionCharging mutation targeting the past-due subscription

Arrange the complete dashboard with a header metrics row and four tabs: Transactions, Disputes, Customers, and Subscriptions.

```
// Daily summary transformer — computes today's metrics from transactions data
// Input: transactionsQuery.data (array of formatted transaction rows)
const allTx = transactionsQuery.data || [];
const today = new Date().toLocaleDateString();
const todayTx = allTx.filter(tx => tx.created_at && tx.created_at.includes(today));
const settledToday = todayTx.filter(tx => tx.status === 'SETTLED');
const refundedToday = todayTx.filter(tx => tx.status === 'REFUNDED');
const totalSettledAmount = settledToday.reduce((sum, tx) => sum + (tx.amount_numeric || 0), 0);
return {
  transaction_count: todayTx.length,
  settled_count: settledToday.length,
  settled_amount: '$' + totalSettledAmount.toFixed(2),
  refunds_today: refundedToday.length,
  refund_amount: '$' + refundedToday.reduce((sum, tx) => sum + (tx.amount_numeric || 0), 0).toFixed(2)
};
```

**Expected result:** Summary statistics show today's settled amount, transaction count, open disputes, and refunds. The Subscriptions tab shows active and past-due subscriptions with cancel and retry options. The full four-tab dashboard provides complete Braintree payment operations in one Retool app.

## Best practices

- Store your Braintree Public Key and Private Key as secret configuration variables in Retool Settings → Configuration Variables — reference them in the resource's Basic Auth fields to avoid hardcoding credentials
- Create separate Retool resources for sandbox and production Braintree environments with clearly labeled names ('Braintree Sandbox', 'Braintree Production') to prevent agents from accidentally running operations against the wrong environment
- Use Braintree's GraphQL API over the legacy REST API where possible — GraphQL queries are more flexible, return exactly the fields requested, and will be Braintree's primary supported API going forward
- Add the Braintree-Version header to your resource with a specific date to ensure your queries don't break when Braintree releases schema changes — pin to a stable version and update intentionally
- Always require a confirmation dialog (Confirm before running) for refund, void, and subscription cancellation mutations — these are irreversible payment operations that should not be triggered accidentally
- Log all refund, void, and subscription cancellation actions to an internal audit table storing the agent's email, transaction ID, action type, amount, timestamp, and result — this creates a compliance trail for payment operations
- Monitor Braintree dispute reply deadlines using a Retool Workflow with a daily schedule that alerts your team when disputes have fewer than 5 days remaining before their response deadline

## Use cases

### Build a transaction search and refund panel

Create a Retool billing support tool where agents can search Braintree transactions by customer email, transaction amount, date range, or payment method. Display results with transaction ID, amount, status, payment method type, and creation date. Add a one-click refund button that triggers a refund query for the selected transaction with an amount input (defaulting to the full transaction amount) and a confirmation dialog.

Prompt example:

```
Build a Braintree transaction search panel with filters for customer email, date range, status (Authorized, Settled, Voided, Refunded), and minimum/maximum amount. Display matching transactions in a table with transaction ID, customer name, amount, payment method (Visa, PayPal, Venmo), status, and settlement date. When a transaction is selected, show a detail panel with payment method details, billing address, and risk data. Add a 'Issue Refund' button with an amount input (defaulting to full transaction amount) and confirmation dialog.
```

### Build a dispute management dashboard

Create a Retool panel for managing Braintree disputes — chargebacks and retrievals initiated by customers. Display open disputes by status (Open, Won, Lost, Expired), amount at risk, and reply-by date. Add filters for urgent disputes approaching their response deadline, and a workflow for marking dispute evidence as submitted and updating internal dispute tracking records.

Prompt example:

```
Build a dispute management panel showing all open Braintree disputes sorted by reply-by date (ascending, so most urgent appear first). Display dispute ID, amount, transaction date, dispute reason, status, and days remaining until reply deadline. Color-code rows: red for disputes with fewer than 3 days remaining, yellow for 4-7 days, green for 8+ days. Add a 'Log Evidence Submitted' button that records the action in the internal disputes_log table and updates the dispute's internal status to 'Responded'.
```

### Build a customer payment history panel

Create a Retool customer service panel that lets support agents search Braintree customers by email, view their stored payment methods (credit cards, PayPal accounts), and see their full transaction history. Add the ability to vault or delete stored payment methods and view subscription status for customers on recurring billing plans.

Prompt example:

```
Build a customer payment panel with an email search input. When a customer is found, display their vaulted payment methods (card type, last four digits, expiry, PayPal email) and their transaction history table (last 50 transactions with amount, status, and date). Add a 'Remove Payment Method' button with confirmation. For subscription customers, show their active subscription with plan name, price, billing cycle, and next billing date.
```

## Troubleshooting

### All GraphQL queries return 401 Unauthorized

Cause: The Basic Auth credentials are incorrect — commonly the Public Key and Private Key are swapped, or the credentials belong to the wrong environment (sandbox vs. production).

Solution: Verify the Basic Auth username is your Braintree Public Key (not the Merchant ID) and the password is your Private Key. Confirm the resource Base URL matches your credential environment: sandbox.braintreegateway.com for sandbox keys, payments.braintreegateway.com for production keys. Using sandbox keys against the production URL (or vice versa) returns 401 errors.

### GraphQL query returns errors array with 'Field does not exist' or 'Unknown argument' messages

Cause: The GraphQL query references fields or arguments that don't exist in Braintree's schema version specified by the Braintree-Version header, or the field names are incorrect.

Solution: Check the Braintree-Version header in your resource is set to a valid date (e.g., 2019-01-01). Verify field names in your GraphQL query against Braintree's current GraphQL API reference at developer.paypal.com/braintree/docs/graphql. Field names in Braintree's schema use camelCase — confirm there are no typos. Use Braintree's GraphQL Explorer (available in the Braintree Control Panel → Tools → GraphQL API Explorer) to test queries interactively.

### Refund mutation returns 'Transaction status must be Settled or Settling' error

Cause: The refundTransaction mutation can only be called on transactions in SETTLED or SETTLING status. Transactions in AUTHORIZED, SUBMITTED_FOR_SETTLEMENT, or VOIDED status cannot be refunded — they must be voided instead.

Solution: Check the transaction's status before offering the refund option. Add a conditional to disable the Refund button unless status is SETTLED or SETTLING. For AUTHORIZED transactions that haven't settled yet, offer a 'Void' button instead — use the voidTransaction mutation. For SUBMITTED_FOR_SETTLEMENT transactions, check whether to void before settlement completes or wait for settlement and then refund.

### Customer search returns empty results even though the customer exists in Braintree

Cause: Braintree's customer search in the GraphQL API requires exact matches for some fields. Email search with the 'is' operator requires an exact case-insensitive match — partial email searches are not supported via GraphQL.

Solution: Verify the customer email is entered exactly as stored in Braintree (lowercase recommended). If your team needs partial-match customer search, use Braintree's legacy REST search endpoint (POST /merchants/{merchantId}/customers/advanced_search_ids with XML body) which supports 'contains' and 'starts_with' operators, then fetch full customer details for returned IDs.

## Frequently asked questions

### Should I use Braintree's GraphQL API or the legacy REST API in Retool?

Braintree recommends using the GraphQL API for new integrations — it provides more flexible querying, returns only the fields you request, and receives new features first. The GraphQL endpoint (/graphql) is a single POST endpoint that accepts all operations via query variables. The legacy REST API (XML-based in some areas) is still supported and may be needed for operations not yet exposed via GraphQL, such as advanced customer searches using partial matching.

### How do I connect to Braintree's sandbox environment versus production?

Create two separate Retool REST API Resources: one with Base URL https://payments.sandbox.braintreegateway.com and sandbox API keys, another with Base URL https://payments.braintreegateway.com and production API keys. Sandbox and production are completely separate environments with different API key sets. Never use production keys in the sandbox resource or vice versa. In Retool, use the Retool environment feature to automatically switch between sandbox and production resources based on the current environment.

### Can Retool access Braintree's vaulted payment methods for updating or deleting cards?

Yes. Braintree's vault stores payment method tokens (credit cards, PayPal accounts, etc.) that can be retrieved and managed via the GraphQL API. Query paymentMethods for a customer to list their vaulted methods, and use the deletePaymentMethod mutation to remove a payment method token. Never display raw card numbers from the vault — Braintree only returns masked data (last four digits, expiration) which is appropriate for display in Retool.

### Does Braintree support webhook events that I can receive in Retool?

Braintree sends webhook notifications for events like subscription charges, dispute creation, and transaction settlement. To receive these in Retool, create a Retool Workflow with a Webhook trigger URL and configure a Braintree webhook destination pointing to that URL in Braintree Control Panel → Settings → Processing → Webhooks. The Workflow can then parse the Braintree webhook notification (which uses a Base64-encoded XML format) and store relevant events in your internal database for display in your Retool dashboard.

### How do I handle Braintree's multi-merchant account setup in Retool?

Braintree accounts can have multiple merchant accounts (sub-accounts for different business divisions, currencies, or risk profiles). Transaction queries can be filtered by merchantAccountId to scope data to a specific merchant account. Add a Merchant Account selector dropdown to your Retool dashboard populated from a static list of your account IDs, and include the selected merchant account ID as a filter in your transaction and dispute search variables.

---

Source: https://www.rapidevelopers.com/retool-integrations/braintree
© RapidDev — https://www.rapidevelopers.com/retool-integrations/braintree
