PayPal Payout
Pay out approved wallet withdrawals to a customer's PayPal email automatically.
Setting up?
Skip to Payout Settings → PayPal for the step-by-step credentials + webhook walkthrough.
What It Does
| For | Means |
|---|---|
| Customer | Picks "PayPal" as the withdrawal method, enters their PayPal email. Gets paid into PayPal once admin approves |
| Admin | One-click approve → plugin calls PayPal's Payouts API. No spreadsheet exports, no manual transfers |

What You Need
- A PayPal Business account
- API credentials (Client ID + Secret) from your PayPal app
- Webhook URL registered in your PayPal app (for status sync)
Setup
1. Get API credentials
- Log in to developer.paypal.com
- Apps & Credentials
- Create app (sandbox first, then live)
- Copy Client ID + Secret
2. Plugin settings
Wallet → Settings → Payout → PayPal
| Field | Notes |
|---|---|
| Enable | toggle exposure on the withdrawal method radio |
| Mode | sandbox for testing / live for production |
| Sandbox Client ID | from your sandbox app |
| Sandbox Secret | from your sandbox app |
| Live Client ID | from your live app |
| Live Secret | from your live app |
| Currency | base currency for payouts |
| Sender batch prefix | e.g. WK-WDRW- (helps reconcile in PayPal dashboard) |
| Webhook URL | shown read-only — copy into PayPal app webhooks |
3. Register the webhook
In your PayPal app settings → Webhooks → add the URL the plugin shows. Subscribe to:
PAYMENT.PAYOUTS-ITEM.SUCCEEDEDPAYMENT.PAYOUTS-ITEM.FAILEDPAYMENT.PAYOUTS-ITEM.REFUNDEDPAYMENT.PAYOUTS-ITEM.RETURNEDPAYMENT.PAYOUTS-ITEM.UNCLAIMED
PayPal will POST to that URL on each event — plugin auto-updates the matching withdrawal status.
How Approval Works
When admin clicks Approve on a PayPal-method withdrawal:
- Plugin calls PayPal Payouts with the customer's amount + email
- PayPal returns batch + item IDs (saved for reconciliation)
- Status flips
pending→approved - When PayPal webhook fires
PAYMENT.PAYOUTS-ITEM.SUCCEEDED→ status flips topaid - On failure → status
failed, admin can retry
Sandbox vs Live
| Mode | Use for | Customer email |
|---|---|---|
sandbox | dev / staging | sandbox personal accounts (created in developer dashboard) |
live | production | real PayPal email of the customer |
Mode mismatch
Sandbox creds with live mode (or vice versa) → "Authentication failed (invalid_client)" error. The plugin guards against the mistake by validating the token endpoint on save.
Currency
PayPal Payouts requires the payout currency to match the receiver's PayPal account, OR the sender's account must support cross-currency payouts.
If a mismatch happens:
- PayPal returns
RECEIVER_CURRENCY_MISMATCHorRECEIVER_UNREGISTERED - Plugin marks the row
failed - Admin can reject → refund → ask customer to fix
Sender Balance Required
PayPal Payouts withdraw from your PayPal Business balance. Top up your PayPal account before approving large withdrawals — failure mode is INSUFFICIENT_FUNDS.
Reconciliation
The plugin tags every payout with the sender batch ID prefix you set. Easy to find a payout in your PayPal dashboard:
search PayPal dashboard for: WK-WDRW-
Or pull from the database:
SELECT id, amount, fee, JSON_EXTRACT(error, '$.payout_batch_id') AS paypal_batch
FROM wp_wkwc_wallet_withdrawal
WHERE status = 'paid' AND method = 'paypal'
AND paid_at >= '2026-04-01';
When Something Goes Wrong
| Problem | Fix |
|---|---|
Authentication failed (invalid_client) | Sandbox creds in live mode, or vice versa. Re-check Mode toggle |
RECEIVER_UNREGISTERED | Customer has no PayPal account at that email. Reject + ask customer to update |
INSUFFICIENT_FUNDS | Your PayPal Business balance too low. Top up + retry from the row drawer |
| Webhook never fires | URL not registered in PayPal app, OR firewall blocking PayPal's IPs |
Status stuck on approved | Webhook URL wrong, or events not subscribed. Re-register events |
For developers — hooks + reconciliation
Hooks
| Hook | Type | When |
|---|---|---|
wkwp_paypal_payout_request_body | filter | mutate the JSON body before send |
wkwp_paypal_payout_response | action | after PayPal API response |
wkwp_paypal_webhook_event | action | every webhook event received |
API call shape
POST /v1/payments/payouts
{
"sender_batch_header": {
"sender_batch_id": "WK-WDRW-<row_id>",
"email_subject": "<subject>"
},
"items": [{
"recipient_type": "EMAIL",
"amount": { "value": "X.XX", "currency": "USD" },
"receiver": "[email protected]",
"note": "Withdrawal #<row_id>"
}]
}
Response stored on the withdrawal row's metadata for reconciliation.
