> ## Documentation Index
> Fetch the complete documentation index at: https://docs.standalonetech.com/llms.txt
> Use this file to discover all available pages before exploring further.

# PayPal Withdrawal Setup

> Step-by-step guide to paying out TeraWallet Pro withdrawals through PayPal Payouts: create a REST app, enable Payouts, configure credentials, register the webhook, and test in sandbox.

This guide walks through connecting **PayPal Payouts** to the TeraWallet Pro [Wallet Withdrawal](/terawallet-pro/withdrawal)
module, so approved requests are sent straight to the customer's PayPal account and final status is
reported back automatically.

<Info>
  Requires TeraWallet Pro with the Withdrawal module active, and a **PayPal Business** account with
  **Payouts** enabled. PayPal's API calls are made server-side, so your site needs working outbound HTTPS.
</Info>

## How it works

1. The customer requests a withdrawal; the wallet is debited the **requested** amount.
2. When the request is approved (automatically, or by you), TeraWallet calls the PayPal **Payouts API**
   with the amount minus your processing fee.
3. If PayPal accepts the batch (status `PENDING`, `PROCESSING`, `SUCCESS` or `NEW`), the request moves to
   **Approved**.
4. PayPal later sends a **webhook** with the final result. On failure, TeraWallet cancels the request and
   restores the customer's balance.

## Step 1 — Create a PayPal REST app

1. Sign in to the [PayPal Developer Dashboard](https://developer.paypal.com/dashboard/applications).
2. Switch the toggle to **Sandbox** (for testing) or **Live** (for real payouts).
3. Go to **Apps & Credentials → Create App**, choose type **Merchant**, and name it (e.g. "TeraWallet Payouts").
4. Open the app and copy the **Client ID** and **Secret key**.
5. Under **Features**, tick **Payouts** and save.

<Warning>
  Without the Payouts feature on the app (and Payouts approved on the Business account for live mode),
  PayPal rejects every payout request. Sandbox and Live use **separate** credentials — never mix them.
</Warning>

## Step 2 — Fund the PayPal account

Payouts are drawn from the PayPal balance of the account that owns the app. Keep enough balance to cover
pending withdrawals. In sandbox, add funds to your sandbox **Business** account from the dashboard under
**Testing Tools → Sandbox Accounts**.

## Step 3 — Configure TeraWallet

Go to **TeraWallet → Settings → Withdrawal**.

**General options**

| Setting | What to do |
| - | - |
| **Enable Withdrawal** | Turn on. |
| **Approval Mode** | *Automatic (instant)* pays out as soon as a request is submitted. *Manual (admin review)* waits for you to approve. Start with Manual while testing. |
| **Minimum / Maximum Withdrawal** | Optional bounds per request. |
| **Enable logging** | Turn on while setting up (see [Troubleshooting](#troubleshooting)). Turn off afterwards — logs may contain personal data. |

**PayPal gateway options**

| Setting | What to do |
| - | - |
| **Enable PayPal** | Turn on. |
| **Mode** | *Sandbox / Test* or *Live / Production*, matching the credentials you copied. |
| **PayPal Client ID** | Paste from Step 1. |
| **PayPal Secret Key** | Paste from Step 1. |
| **PayPal Webhook ID** | Filled in during Step 4. |
| **Enable Processing Fee**, **Fee Type**, **Fee Amount**, **Fixed Component** | Optional. The fee is deducted from the payout, not added to the debit: the wallet is debited the requested amount and PayPal receives `amount − fee`. |

Save your changes.

## Step 4 — Register the webhook

Webhooks tell TeraWallet whether a payout actually succeeded or later failed.

1. In the PayPal app, scroll to **Webhooks → Add Webhook**.
2. Set the **Webhook URL** to:

   ```
   https://your-site.com/wp-json/terawallet/v1/paypal/webhook
   ```

   The URL must be public HTTPS. Use the **same** Sandbox/Live mode as your credentials.
3. Subscribe to these events:

   | Event | Effect in TeraWallet |
   | - | - |
   | `PAYMENT.PAYOUTS-ITEM.SUCCEEDED` | Marks the request **Approved** and adds a "PayPal payout succeeded" note. |
   | `PAYMENT.PAYOUTS-ITEM.DENIED` | Cancels the request and restores the balance. |
   | `PAYMENT.PAYOUTS-ITEM.BLOCKED` | Same as above. |
   | `PAYMENT.PAYOUTS-ITEM.FAILED` | Same as above. |
   | `PAYMENT.PAYOUTS-ITEM.REFUNDED` | Same as above. |
   | `PAYMENT.PAYOUTS-ITEM.RETURNED` | Same as above. |
   | `PAYMENT.PAYOUTS-ITEM.REVERSED` | Same as above. |
   | `PAYMENT.PAYOUTS-ITEM.UNCLAIMED` | Same as above. |
   | `PAYMENT.PAYOUTSBATCH.DENIED` | Same as above (batch-level). |

   Other event types are acknowledged and ignored.
4. Save, then copy the generated **Webhook ID** into **PayPal Webhook ID** in TeraWallet and save.

<Note>
  Every webhook is verified against PayPal's signature-verification API using your Webhook ID and
  credentials. If the Webhook ID is empty, or the signature is invalid, the request is rejected and no
  withdrawal changes. Cancelled requests have their wallet debit reversed, so the customer is made whole.
</Note>

## Step 5 — What customers need to do

Customers add their PayPal email once, under **My Account → Wallet → Withdrawal → Payment Settings**
(field: **PayPal email**). If it is missing, the withdrawal form tells them to set it up before they can
submit. The payout goes to this email address, so make sure customers use the one tied to their PayPal
account.

## Step 6 — Test in sandbox

1. Set **Mode** to *Sandbox / Test* and use sandbox credentials and a sandbox webhook.
2. In the dashboard, create a sandbox **Personal** account; use its email as the test customer's PayPal email.
3. As that customer, with a wallet balance, submit a withdrawal.
4. In **wp-admin**, open the withdrawal list and **Approve** the request (or let Automatic mode do it).
5. Check the request: it should be **Approved**, with log entries showing the PayPal response.
6. Use **Webhooks → Simulate event** in the PayPal dashboard (e.g. `PAYMENT.PAYOUTS-ITEM.SUCCEEDED`, then a
   failure event on a second request) to confirm status changes and notes.
7. Log in to the sandbox personal account at [sandbox.paypal.com](https://www.sandbox.paypal.com) to see the
   received funds.

## Go live checklist

* [ ] Payouts approved on the live PayPal Business account and enabled on the live app
* [ ] **Mode** switched to *Live / Production*
* [ ] Live **Client ID** and **Secret Key** pasted
* [ ] A **new live webhook** created, with its Webhook ID pasted (sandbox IDs do not work live)
* [ ] PayPal balance funded
* [ ] **Enable logging** turned off
* [ ] One small real withdrawal tested end to end

## Duplicate-payment protection

Each withdrawal is sent with a stored unique batch ID (also used as PayPal's `PayPal-Request-Id`), so a
retry after a timeout is treated by PayPal as the same request and cannot pay twice.

## Troubleshooting

Enable **Enable logging**, then open the request in the withdrawal list to see its gateway log. Failure
reasons are also saved as notes on the request.

| Symptom / message | Likely cause |
| - | - |
| "Unable to approve request please check log file" | Gateway call failed. See the note on the request for the reason below. |
| "PayPal payout skipped: missing client credentials or recipient email." | Client ID/Secret empty, or the customer has no PayPal email saved. |
| "could not obtain OAuth access token" | Wrong credentials, or Sandbox/Live mode mismatch with the keys. |
| "PayPal payout rejected: …" | PayPal returned an error, commonly Payouts not enabled on the app/account, insufficient balance, or unsupported currency. The message is PayPal's own. |
| "PayPal payout transport error" | Your server could not reach `api-m.paypal.com` (firewall, DNS, cURL/SSL). |
| Request stays Approved but money never arrives | Webhook not configured; check the Webhook ID, URL and event subscriptions. |
| Webhook returns 400/500/502 | 500 `missing_webhook_id` / `missing_credentials`: fill the settings. 400 `invalid_signature`: Webhook ID belongs to a different app or mode. 502: could not verify with PayPal. |

<Tip>
  Hosts that block unauthenticated REST requests (security plugins, WAFs, "REST API disabled" settings)
  can stop PayPal from reaching the webhook URL. Allow `POST /wp-json/terawallet/v1/paypal/webhook`.
</Tip>

## Developer notes

* The payout uses PayPal Payouts v1, with the recipient type `EMAIL` and the withdrawal ID as the `sender_item_id`.
* Filter `woo_wallet_paypal_payouts_sync_mode` (default `false`) switches Payouts to synchronous mode.
* Filter `woo_wallet_withdrawal_payout_batch_status` changes which batch statuses count as accepted
  (default `PENDING`, `PROCESSING`, `SUCCESS`, `NEW`).

See [Pro Hooks & REST API](/terawallet-pro/hooks) for the full reference.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.