Automated PayPal Sales to Xero Reconciliation (n8n)
Eliminate manual bookkeeping by automatically syncing PayPal transactions, fees, and contacts to Xero using n8n workflows.
Tools: PayPal → Xero
Platform: n8n
Short Answer
A fully automated FinOps pipeline where every PayPal sale triggers contact creation, invoice generation, and payment application in Xero. This ensures real-time P&L accuracy and effortless month-end reconciliation of transaction fees.
The Problem
Manual entry of PayPal transactions into Xero is time-consuming and prone to errors, particularly regarding merchant fee calculations. Without automation, bank reconciliation becomes a nightmare as the gross amount received in Xero doesn't match the net amount deposited in the bank.
The Outcome
A fully automated FinOps pipeline where every PayPal sale triggers contact creation, invoice generation, and payment application in Xero. This ensures real-time P&L accuracy and effortless month-end reconciliation of transaction fees.
Step-by-Step Guide
1. **Configure PayPal Webhook**: Start with a 'Webhook' node in n8n. Set the HTTP Method to POST. In your PayPal Developer Dashboard, point the Webhook URL to your n8n production URL and select the `PAYMENTS.PAYMENT.COMPLETED` event.
2. **Set Up Credentials**: In n8n, navigate to 'Credentials'. Create a 'PayPal API' credential (using Client ID/Secret) and a 'Xero OAuth2 API' credential by creating an app in the Xero Developer Portal.
3. **Search for Xero Contact**: Add a 'Xero' node. Set the Resource to 'Contact' and Operation to 'Get All'. Use a Filter Expression to search by `{{ $json.payer.payer_info.email }}`.
4. **Conditional Logic (IF Node)**: Add an 'IF' node to check if the search returned a contact. If not, branch to another Xero node to 'Create Contact' using the name and email from the PayPal payload.
5. **Data Transformation (Code Node)**: Use a 'Code' node to perform math. Xero requires the Gross Amount and the PayPal Fee to be handled. Create variables: `const netLabel = 'PayPal Fee'; const feeAmount = parseFloat($json.transaction_fee.value) * -1;`. This ensures the fee is treated as an expense.
6. **Create Authorized Invoice**: Add a 'Xero' node. Resource: 'Invoice', Operation: 'Create'. Map the Contact ID from Step 3/4. Set 'Status' to 'AUTHORISED' to allow payment application.
7. **Apply Payment**: Add another 'Xero' node. Resource: 'Payment', Operation: 'Create'. Map the `Invoice ID` from the previous step. **Crucial**: Map the PayPal `transaction_id` to the Xero `Reference` field for reconciliation.
8. **Setup Error Handling**: Create an 'Error Trigger' node. Connect it to a 'Gmail' or 'Slack' node to notify you if the Xero API returns a 400 (e.g., due to a locked accounting period).
9. **Deploy**: Switch the workflow from 'Inactive' to 'Active'.
Data Mapping
| PayPal Field | Xero Field | n8n Expression / Logic |
| :--- | :--- | :--- |
| `payer.payer_info.email` | `Contact Email` | `{{ $json["payer"]["payer_info"]["email"] }}` |
| `transactions[0].amount.total` | `Invoice Line Amount` | `{{ parseFloat($json["amount"]) }}` (Required) |
| `transaction_fee.value` | `Bank Fee Amount` | `{{ $json["fee"] }}` -> Map to Expense Account 400 |
| `id` | `Reference` | `{{ $json["id"] }}` (Essential for Find & Match) |
| `transactions[0].item_list` | `Line Item Description`| `{{ $json["items"].join(', ') }}` or use Split in Batches |
| `USD` | `Currency` | `{{ $json["currency_code"] }}` (Multi-currency Add-on required in Xero) |
Gotchas & Failure Modes
- **Rate Limiting**: Xero has a limit of 5,000 API calls per day. If syncing high-volume sales, use the 'Split in Batches' node to throttle requests.
- **Rounded Totals**: Ensure you use `toFixed(2)` in n8n expressions; Xero will reject payments where the sum of line items doesn't exactly match the total due to floating-point math.
- **Locked Periods**: The workflow will fail if you try to sync a transaction to a period you have already closed in Xero. Use a 'Try/Catch' approach via Error Trigger.
- **Duplicate Invoices**: Always check for an existing reference in Xero before creating a new invoice to prevent double-counting if PayPal sends duplicate webhooks.
Verification Checklist
- [ ] **Trigger Test**: Send a test webhook from the PayPal Developer Dashboard and verify n8n captures the JSON.
- [ ] **Contact Logic**: Verify that if a contact exists, n8n correctly identifies it instead of creating a duplicate.
- [ ] **Math Check**: Confirm that the Xero Invoice Net + PayPal Fee = PayPal Gross Amount.
- [ ] **Reference Check**: Ensure the PayPal Transaction ID appears in the Xero Invoice Reference field.
- [ ] **Production Mode**: Confirm the n8n Webhook URL is set to `/wait/` (Production) and not `/test/` before going live.
Ready to Automate?
Build this automation with n8n in minutes.