# PayPal Payouts

- Tool: Bubble
- Difficulty: Intermediate
- Time required: 2–3 hours
- Last updated: July 2026

## TL;DR

Connect Bubble to PayPal Payouts by setting up an API Connector group with OAuth 2.0 credentials stored as Private headers — Bubble proxies all calls server-side so your client_id and secret never reach the browser. You'll exchange credentials for a Bearer token, build a payout batch from a list of recipients, and poll for settlement status. Payouts must be explicitly enabled on your PayPal developer app before any API call succeeds.

## How PayPal Payouts Works Inside Bubble

PayPal Payouts uses a two-step API pattern: first exchange your app credentials for a time-limited Bearer token, then use that token to submit a batch of payout recipients. The token lives for roughly nine hours, so for most apps a single scheduled refresh is enough to keep calls authorized around the clock.

Bubble's API Connector runs every call from Bubble's own servers — the same security model as Retool or any traditional backend. This means CORS is never an issue, and you do not need a separate Cloudflare Worker or Netlify Function to hide your PayPal secret. You configure the Private checkbox in the API Connector header once, and Bubble handles the rest.

The most common reason a correctly configured integration fails is that the Payouts feature is not enabled on the PayPal developer app. PayPal ships new apps with Payouts switched off, even in live mode. Enabling it takes one click in the developer dashboard but is easy to miss.

Because this integration involves scheduled workflows and stored tokens, it is best suited for Bubble apps on a paid plan (Starter $32/mo or above). The Free plan allows outbound API Connector calls but cannot run Backend Workflows for receiving PayPal IPN events.

## Before you start

- A PayPal Business account (Payouts is not available on personal accounts)
- A PayPal developer app created at developer.paypal.com with the Payouts feature explicitly toggled on under Live Features
- Your PayPal app's client_id and client_secret (sandbox and production are separate credential sets)
- A base64 encoding tool (browser-based, e.g. base64encode.org) to pre-encode 'client_id:client_secret' — Bubble cannot compute base64 natively
- A Bubble app on a paid plan (Starter $32/mo or above) if you need Backend Workflows for IPN receipt; Free plan supports outbound payout calls only
- The API Connector plugin installed in your Bubble app (Plugins tab → Add plugins → search 'API Connector' by Bubble → Install)

## Step-by-step guide

### 1. Enable the Payouts Feature and Gather Credentials

Start at developer.paypal.com and log in with your PayPal Business account. Click 'My Apps & Credentials' in the left navigation. If you have not yet created an app, click 'Create App', give it a name (e.g. 'My Bubble App'), select 'Merchant' as the account type, and click 'Create App'.

Once the app is created, you will see its sandbox and live credentials. Click into the app's details page. Look for the section called 'Live Features' (on the 'Live' tab) — you will see a list of optional API features. Find 'Payouts' in the list and toggle it on. Without this step, your API calls will return a 403 Forbidden error with the message 'insufficient_scope' even if your credentials are correct. This single step is the most common reason Bubble builders spend hours debugging a working OAuth flow.

Copy your sandbox client_id and client_secret (under the 'Sandbox' tab) for initial testing. You will switch to live credentials after testing. Keep both sets safe — never paste them into a Bubble non-Private field.

Next, pre-compute the base64 encoding of your credentials. Open a base64 tool in your browser (base64encode.org works well). Type your sandbox credentials in this exact format: client_id:client_secret (the actual values, separated by a colon, no spaces). Encode it. Copy the resulting string — it looks like a long string of random characters. You will paste this into the API Connector header in the next step.

**Expected result:** You have a PayPal developer app with Payouts enabled, a pre-encoded base64 string of your sandbox credentials, and both client_id and client_secret values saved securely.

### 2. Configure the API Connector with OAuth Token Exchange

In your Bubble app editor, click the 'Plugins' tab in the left sidebar. Click 'Add plugins', type 'API Connector' in the search box, find 'API Connector' by Bubble (it is free), and click 'Install'. Close the plugin marketplace.

Back in the Plugins tab, click on 'API Connector'. Click 'Add another API'. In the 'API Name' field type 'PayPal Payouts'. This name labels the entire group of related PayPal calls.

Now add the token exchange call. Click 'Add another call'. Name this call 'Get Token'. Set the method to 'POST'. In the URL field, enter: https://api-m.sandbox.paypal.com/v1/oauth2/token

Scroll down to the Headers section. Click 'Add header'. In the key field type 'Authorization'. In the value field type 'Basic ' followed by the pre-encoded base64 string you computed in Step 1 (example: 'Basic dXNlcjpwYXNzd29yZA=='). Check the 'Private' checkbox next to this header. The Private checkbox is critical — it tells Bubble to strip this header from any browser-facing debug data and only send it from Bubble's servers.

Click 'Add header' again. Key: 'Content-Type'. Value: 'application/x-www-form-urlencoded'. Leave this one unchecked (it is not sensitive).

Scroll down to the Body section. Switch the body type to 'Form Data (x-www-form-urlencoded)'. Add one key-value pair: key 'grant_type', value 'client_credentials'.

Click 'Initialize call'. Bubble sends a real request to PayPal's sandbox. If successful, PayPal returns a JSON response including 'access_token', 'expires_in' (32400 seconds, about 9 hours), and 'token_type'. Bubble reads this response and auto-detects the field types. Click 'Save' on the response. If initialization fails with 'There was an issue setting up your call', double-check the base64 encoding — a single character error breaks it.

Set 'Use as' to 'Action' (not Data) — you will call this from a workflow, not bind it to a data source.

```
{
  "method": "POST",
  "url": "https://api-m.sandbox.paypal.com/v1/oauth2/token",
  "headers": {
    "Authorization": "Basic <private: base64(client_id:client_secret)>",
    "Content-Type": "application/x-www-form-urlencoded"
  },
  "body": "grant_type=client_credentials"
}
```

**Expected result:** The 'Get Token' call is saved and initialized. Bubble shows a green check. The detected response fields include 'access_token' and 'expires_in'.

### 3. Store the Token and Set Up Automatic Refresh

Your Bubble app needs to store the access token so all payout workflows can use it without fetching a new token on every call. Create a dedicated storage data type for this.

Go to the Data tab in Bubble. Click 'New type' and name it 'App Config'. Add two fields: 'paypal_token' (type: text) and 'paypal_token_expires' (type: date). You will store one single row of this type — it acts like a key-value config store for the whole app.

Set Privacy Rules for the App Config type: Data tab → Privacy → click 'App Config' → set the rule so no user can view any field of this type unless they are an admin (add a condition: 'Current User's role is admin' or use a custom 'is_admin' boolean field on the User type). This prevents token leakage through Bubble's public API endpoints.

Now create a workflow to fetch and store the token. In the Workflow editor, create a new workflow triggered by 'When page loads' on an admin-only page, or as a Backend Workflow callable from other workflows. The workflow should: (1) call the 'PayPal Payouts - Get Token' API Connector action, (2) search for the existing App Config record (or create one if it doesn't exist), (3) update 'paypal_token' with the access_token value from the API response, and (4) update 'paypal_token_expires' with 'Current date/time + 32400 seconds' (use Bubble's date calculator: now + 32,400 seconds = now + 9 hours).

Next, set up automatic token refresh. In the Workflow editor, click 'Backend workflows'. Create a new recurring event named 'Refresh PayPal Token'. Set it to run every 8 hours (safely within the 9-hour expiry window). The recurring event workflow performs the same three steps: call Get Token → update App Config → update expires_at. Note: recurring events require a paid Bubble plan. If you're on the Free plan, manually trigger the token refresh from an admin page before running payouts.

**Expected result:** An 'App Config' data type exists with Privacy Rules applied, the PayPal token is stored after the first manual call, and a recurring workflow refreshes it every 8 hours automatically.

### 4. Configure the Payout Batch API Call

Now add the main payout call to your PayPal Payouts API Connector group. In the Plugins tab, click 'API Connector' → find your 'PayPal Payouts' API group → click 'Add another call'. Name this call 'Send Payout Batch'. Set method to 'POST'. URL: https://api-m.sandbox.paypal.com/v1/payments/payouts

In the Headers section, click 'Add header'. Key: 'Authorization'. Value type this exactly: 'Bearer <dynamic>' — you will make this dynamic in a moment. Check the 'Private' checkbox. Add another header: 'Content-Type' with value 'application/json'.

For the Authorization header value, you need to inject the stored access token dynamically. In Bubble's API Connector, set the header value to 'Bearer ' followed by a dynamic parameter. Click the gear icon or angle-bracket icon next to the header value to mark it as a parameter, name the parameter 'access_token', and set its default to a placeholder string. When you call this action from a workflow, Bubble will let you supply the real token from your App Config data.

In the Body section, choose 'JSON / Custom' body type. Paste the following template — you will replace the static values with dynamic parameters:

Mark the following fields as API Connector parameters (using the parameter syntax): sender_batch_id, email_subject, the full items array. In practice, you will build the items JSON array in a preceding workflow step using 'Compose JSON' or by looping through a list and accumulating the array.

Set 'Use as' to 'Action'. Click 'Initialize call' — provide a realistic sandbox batch ID and one real sandbox recipient email (PayPal provides test accounts in the sandbox dashboard). A successful response returns a 201 status with a 'links' array containing the payout_batch_id.

```
{
  "method": "POST",
  "url": "https://api-m.sandbox.paypal.com/v1/payments/payouts",
  "headers": {
    "Authorization": "Bearer <dynamic: access_token>",
    "Content-Type": "application/json"
  },
  "body": {
    "sender_batch_header": {
      "sender_batch_id": "<dynamic: batch_id>",
      "email_subject": "<dynamic: email_subject>",
      "email_message": "You have received a payment from our platform."
    },
    "items": [
      {
        "recipient_type": "EMAIL",
        "amount": {
          "value": "<dynamic: amount>",
          "currency": "USD"
        },
        "receiver": "<dynamic: receiver_email>",
        "note": "<dynamic: note>",
        "sender_item_id": "<dynamic: item_id>"
      }
    ]
  }
}
```

**Expected result:** The 'Send Payout Batch' call is saved in the API Connector. Bubble shows a successful initialization response with a payout_batch_id in the response links.

### 5. Build the Payout Trigger Workflow and Status Polling

With both API calls configured, you can now wire them into Bubble workflows that your app actually uses. The trigger workflow assembles a list of recipients from your database and fires the payout batch. The polling workflow checks on settlement status later.

Create a Button element on an admin page (or a scheduled Backend Workflow that fires on a schedule). Label the button 'Send Weekly Payouts'. In the Workflow editor, set up the button's click workflow with these steps:

Step 1 — Retrieve the stored token: Add a 'Search for App Config' step, get the first result, and save the paypal_token value to a workflow state.

Step 2 — Compose the recipients JSON: This is the trickiest part in Bubble. Create a 'Search for Users' or 'Search for Payouts' step that returns everyone eligible for payment (e.g. Users where unpaid_balance > 0). Bubble cannot natively loop an API call over a list inside a single workflow step. The most reliable approach for small lists (under 50 recipients) is to build a comma-separated JSON array manually using Bubble's 'Compose text' with ':format as text' on the list. For larger lists, use Bubble's 'Schedule API workflow on a list' to send one payout per recipient (each as a separate 1-item batch).

Step 3 — Generate batch ID: Use 'Create a random string' from Bubble's toolbox (or use 'Current date/time formatted as YYYYMMDD-HHmmss') and append it to form a unique sender_batch_id.

Step 4 — Call 'PayPal Payouts - Send Payout Batch': Pass the stored access_token, the generated batch_id, the email subject, and the items array. Bubble sends this server-side.

Step 5 — Store the payout batch ID: On success, create a new 'Payout Batch' data type entry with the payout_batch_id (from the response), submission time, and status 'PENDING'.

For status polling, create a Backend Workflow called 'Check Payout Status'. This calls GET /v1/payments/payouts/{payout_batch_id} using a third API Connector call you will add to the PayPal Payouts group. On success, update the 'Payout Batch' record's status field with 'batch_status' from the response. Schedule this workflow to run 60 minutes after the payout is submitted using 'Schedule API workflow' with a 60-minute delay. RapidDev's team has built dozens of Bubble payout flows with this exact architecture — if the batch assembly feels complex for your use case, a free scoping call at rapidevelopers.com/contact can help you choose the right approach for your recipient volume.

```
{
  "method": "GET",
  "url": "https://api-m.sandbox.paypal.com/v1/payments/payouts/<dynamic: payout_batch_id>",
  "headers": {
    "Authorization": "Bearer <dynamic: access_token>",
    "Content-Type": "application/json"
  }
}
```

**Expected result:** Clicking 'Send Weekly Payouts' fires a payout batch, stores the payout_batch_id in Bubble, and a scheduled status workflow updates the batch status 60 minutes later.

### 6. Set Up Backend Workflow for PayPal IPN (Paid Plan)

PayPal's Instant Payment Notification (IPN) system sends POST callbacks to your endpoint when a payout item's status changes (e.g. a recipient claims or unclaims their payment). While status polling covers most use cases, IPN gives you real-time confirmation without repeated GET calls — reducing WU consumption on Bubble's metered pricing.

In your Bubble app, go to Settings in the left sidebar. Click the 'API' tab. Check the box that says 'This app exposes a Workflow API' — this is required to create Backend Workflows that receive HTTP requests. Note: this setting and Backend Workflows are only available on paid Bubble plans (Starter $32/mo and above).

Now go to 'Backend workflows' (also accessible from the left sidebar in the Workflow editor). Click 'New API workflow'. Name it 'receive_paypal_ipn'. In the workflow settings, click 'Detect request data' to tell Bubble to listen for an incoming POST and auto-detect the data schema.

In a separate browser tab, go to your PayPal developer dashboard. Under your app settings, find the 'Webhooks' section. Add a new webhook. In the endpoint URL field, paste your Bubble Backend Workflow URL — it looks like: https://your-app-name.bubbleapps.io/api/1.1/wf/receive_paypal_ipn

For events, select 'PAYMENT.PAYOUTS-ITEM.SUCCEEDED', 'PAYMENT.PAYOUTS-ITEM.FAILED', and 'PAYMENT.PAYOUTS-ITEM.UNCLAIMED'. Save the webhook.

Trigger a test event from PayPal's webhook test tool. Switch back to Bubble — you should see the detected fields populated in your Backend Workflow. Click 'Stop detecting data' and save. Now add workflow steps: search for the matching 'Payout Batch' record using the batch_id from the IPN payload, update its status field with the event type, and optionally send a notification to the relevant user.

Bubble's Backend Workflow endpoint automatically returns a 200 response — you do not need to configure this explicitly. Add a Data tab Privacy rule for any new data types created from IPN data, restricting access to admins only.

```
{
  "paypal_ipn_endpoint": "https://your-app-name.bubbleapps.io/api/1.1/wf/receive_paypal_ipn",
  "subscribe_to_events": [
    "PAYMENT.PAYOUTS-ITEM.SUCCEEDED",
    "PAYMENT.PAYOUTS-ITEM.FAILED",
    "PAYMENT.PAYOUTS-ITEM.UNCLAIMED"
  ],
  "sample_ipn_payload": {
    "id": "WH-1234567890",
    "event_type": "PAYMENT.PAYOUTS-ITEM.SUCCEEDED",
    "resource": {
      "payout_item_id": "ABCD1234",
      "transaction_id": "TXN-5678",
      "payout_batch_id": "BATCH-9012",
      "payout_item": {
        "receiver": "recipient@example.com",
        "amount": { "currency": "USD", "value": "10.00" }
      },
      "transaction_status": "SUCCESS"
    }
  }
}
```

**Expected result:** Your Backend Workflow receives PayPal IPN events, updates payout item status in real time, and Bubble responds with 200 automatically. No polling WU cost for confirmed events.

## Best practices

- Always mark the Authorization header as 'Private' in Bubble's API Connector — this is the only thing standing between your PayPal credentials and browser-visible network requests.
- Set Data tab Privacy Rules on any data type that stores paypal_token, payout_batch_id, or receiver email addresses. Restrict all fields to the record's owning user or admin role — Bubble's default is fully public.
- Use a unique sender_batch_id for every batch by combining the current timestamp with a random string. Bubble's 'Create a random string' action is the simplest way to guarantee uniqueness without an external service.
- Test the complete flow end-to-end in PayPal's sandbox before switching to live credentials. PayPal provides test recipient accounts in the sandbox dashboard — use them to verify the payout reaches the recipient side.
- Budget for Workload Units (WU) if you run high-frequency payout workflows. Each API Connector call costs WU. For apps sending payouts daily, the recurring token refresh (3 calls/day) plus status polls add up — monitor Bubble's WU dashboard under the Logs tab.
- For large recipient lists (50+ users), avoid building a single massive items JSON array in one workflow step. Instead, use 'Schedule API workflow on a list' to send individual single-recipient batches — this is more reliable and easier to retry on failure.
- Never share or log the PayPal access_token in user-visible pages or Bubble debug states. Treat it with the same sensitivity as the client_secret — store only in the App Config data type with admin-only privacy rules.
- Always test the Payouts feature toggle in both sandbox and live environments before going to production. A common mistake is enabling Payouts on the live app but forgetting to enable it on the sandbox app too, causing sandbox testing to fail with 403 errors.

## Use cases

### Referral and Cashback Programs

Disburse referral bonuses and cashback rewards to users who linked their PayPal email at signup. When a reward threshold is reached, your Bubble workflow assembles a payout batch and fires one API call to send everyone their balance at once.

Prompt example:

```
Build a referral rewards page in Bubble where users see their pending balance, and an admin can trigger a batch PayPal payout to all eligible users who reached the $10 minimum threshold.
```

### Contest and Giveaway Prize Payments

Pay competition winners automatically the moment results are finalized. A Bubble workflow reads the winners list from your database, builds the recipients JSON array, and submits the payout batch — no manual PayPal transfers needed.

Prompt example:

```
Create a Bubble admin workflow that reads all winners from a 'Contest' data type where status is 'winner', builds a PayPal payout batch with each winner's email and prize amount, and stores the payout batch ID for tracking.
```

### Gig Marketplace Worker Payments

Pay freelancers or task workers on a weekly or bi-weekly schedule. Your Bubble app accumulates completed task earnings in a database, then a scheduled workflow submits a payout batch every Friday — covering everyone at once with a single API call.

Prompt example:

```
Design a Bubble weekly payment workflow that finds all 'Worker' users with unpaid earnings above $5, creates a PayPal payout batch, marks their earnings as paid, and schedules a status-check workflow 60 minutes later.
```

## Troubleshooting

### API call returns 403 Forbidden with 'insufficient_scope' even though OAuth token was retrieved successfully

Cause: The Payouts feature is not enabled on your PayPal developer app. PayPal ships all new apps with Payouts disabled — even after app creation and OAuth setup, the feature must be manually toggled on.

Solution: Go to developer.paypal.com → My Apps & Credentials → click your app name → click the 'Live' tab → scroll to 'Live Features' → find 'Payouts' → toggle it on → save. Repeat on the Sandbox tab for sandbox testing. Re-test your API call after enabling — no code changes needed, the token you already have will work.

### Bubble shows 'There was an issue setting up your call' when initializing the Get Token call

Cause: The base64-encoded Authorization header value is malformed. Even one wrong character in the base64 string causes a 401 from PayPal, which prevents Bubble from parsing the response for initialization.

Solution: Re-encode your credentials carefully. The format must be exactly: client_id:client_secret — the actual credential values with no spaces before or after the colon. Use base64encode.org, paste the combined string, encode it, and paste the result after 'Basic ' in the header value (note the space between 'Basic' and the encoded string). Verify the header value looks like: 'Basic dXNlcjpwYXNzd29yZA=='

### Payout batch submission returns 400 with 'DUPLICATE_REQUEST_ID'

Cause: The sender_batch_id you passed in the request body was used in a previous payout batch. PayPal treats batch IDs as permanent unique identifiers and rejects any reuse, even across different test runs.

Solution: Generate a new unique batch ID for every payout submission. In Bubble, combine the current date/time formatted as text with a random string: use 'Current date/time:formatted as YYYYMMDD' + '-' + Bubble's 'Create a random string' (8 characters). Store this generated ID before calling the API so you can reference it in the status poll workflow.

### Payout call returns 401 Unauthorized during production batch submission, even though the token was recently refreshed

Cause: The stored access_token has expired or the recurring refresh workflow was delayed by Bubble's scheduler. Bubble's recurring events can occasionally run slightly late, and if the token expires before the next refresh cycle, all payout calls fail with 401.

Solution: Add a token validity check at the start of every payout workflow. Before calling 'Send Payout Batch', add a Conditional step: 'Only when App Config's paypal_token_expires < Current date/time + 5 minutes'. If this condition is true, trigger 'Get Token' first and update the stored token. This defensive pattern catches any scheduler delay before it causes a failed payout.

### Backend Workflow URL does not appear under Settings → API

Cause: Backend Workflows are a paid-plan feature. If you are on the Bubble Free plan, the 'Expose a Workflow API' checkbox does not appear — the setting is hidden for free accounts.

Solution: Upgrade to Bubble Starter ($32/mo) or above to enable Backend Workflows and IPN receipt. As a Free plan alternative for settlement confirmation, use the polling approach from Step 5: schedule a GET /v1/payments/payouts/{id} call 60 minutes after submission and update payout status from the response.

## Frequently asked questions

### Can I use PayPal Payouts on the Bubble Free plan?

Outbound payout calls (sending the batch via API Connector) work on the Free plan because the API Connector itself is free. However, receiving PayPal IPN callbacks via Backend Workflows requires a paid plan (Starter $32/mo or above). For the Free plan, use the status polling approach instead: schedule a GET call 60 minutes after submission to check settlement status.

### Does this integration handle PayPal Checkout (receiving payments from customers)?

No. This page covers PayPal Payouts, which sends money TO recipients. PayPal Checkout — where customers pay your platform — is a completely different API and typically handled with a PayPal plugin or Braintree integration. Payouts is exclusively for disbursement workflows like referral rewards, cashback, and worker payments.

### How many recipients can I include in a single payout batch?

PayPal allows up to 15,000 items per payout batch. For most Bubble apps, the practical limit is much lower — assembling a JSON array of more than 50–100 recipients inside a single Bubble workflow becomes unwieldy. For larger lists, split into multiple batches using 'Schedule API workflow on a list' to send a separate 1-item batch per recipient, then track all batch IDs in your database.

### What happens if a recipient does not have a PayPal account?

PayPal sends an email to the recipient's email address inviting them to claim the payment by creating a PayPal account. The payout item status changes to 'UNCLAIMED'. If the recipient does not claim the payment within 30 days, PayPal returns the funds to your PayPal balance and the item status becomes 'RETURNED'. Subscribe to the PAYMENT.PAYOUTS-ITEM.UNCLAIMED IPN event (or poll status) to detect these cases and notify the recipient.

### Is PayPal Payouts available in all countries?

PayPal Payouts is available in the countries where PayPal operates, but recipient countries and currency support vary. US-to-US domestic payouts have the simplest pricing. Cross-border payouts incur additional fees and some destination countries are restricted. Check paypal.com/us/webapps/mpp/merchant-fees for the current fee schedule and developer.paypal.com for supported country/currency combinations before building.

### Can I send different amounts to different recipients in the same batch?

Yes — each item in the payout batch items array has its own amount object with value and currency fields. You can send $10.00 to one recipient and $250.00 to another in the same batch. The value must be formatted as a decimal string (e.g. '10.00' not 10 or 1000). In Bubble, format your amount fields as text before composing the JSON to avoid integer/decimal mismatch errors.

---

Source: https://www.rapidevelopers.com/bubble-integrations/paypal-payouts
© RapidDev — https://www.rapidevelopers.com/bubble-integrations/paypal-payouts
