Automated Revenue and Fee Reconciliation: Stripe to Xero (n8n)

Streamline accounting by syncing Stripe charges, merchant fees, and net payouts directly into Xero using n8n workflows.

Tools: StripeXero

Platform: n8n

Short Answer

A fully automated financial workflow where every successful Stripe payment creates a Xero contact, generates a paid invoice for the gross amount, and logs a separate bank transaction for the Stripe fee, ensuring ledger-perfect reconciliation with zero manual entry.

The Problem

Manual reconciliation of Stripe payments in Xero is time-consuming and prone to errors, especially when accounting for Stripe's processing fees. Businesses often struggle to match the gross payment amount with the net deposit while ensuring tax compliance across both platforms.

The Outcome

A fully automated financial workflow where every successful Stripe payment creates a Xero contact, generates a paid invoice for the gross amount, and logs a separate bank transaction for the Stripe fee, ensuring ledger-perfect reconciliation with zero manual entry.

Step-by-Step Guide

1. **Configure Stripe Webhook**: Add a 'Stripe Trigger' node. Select the `payment_intent.succeeded` event. Set up your Stripe Credentials by copying the Secret Key from the Stripe Dashboard. Use the 'Test' environment first. 2. **Format Data with Set Node**: Use a 'Set' node to convert the Stripe amount (which is in cents) to dollars (e.g., `{{ $json["amount"] / 100 }}`). Use n8n expressions like `$now` to format the invoice date. 3. **Authenticate Xero**: Add a 'Xero' node. Select 'OAuth2' for credentials. Ensure you have 'Sales' and 'Contact' permissions selected in your Xero App portal. 4. **Check for Existing Contact**: In the Xero node, select the 'Contact' resource and 'Get All' operation. Use a Filter to find a contact where the 'Email' matches `{{ $json["receipt_email"] }}` from Stripe. 5. **Branching Logic**: Use an 'IF' node to check if a contact ID was returned. If not, route to a Xero 'Create Contact' node. 6. **Create the Invoice**: Add a Xero node with 'Invoice' resource and 'Create' operation. Map the Invoice Number to the Stripe Charge ID to prevent duplicates. 7. **Apply Payment**: Add a Xero node with the 'Payment' operation. Use the Invoice ID from the previous step and the 'Bank Account' code where your Stripe funds are settled. 8. **Calculate & Log Fees**: Extract the Stripe Fee from the `balance_transaction` object (you may need a separate 'Stripe' node to 'Get' the balance transaction details if not in the webhook). Create a 'Bank Transaction' (Spend Money) in Xero to account for the fee. 9. **Implement Error Handling**: Create a separate 'Error Trigger' workflow or use the 'On Error' settings on individual nodes to catch API timeouts or mapping failures. 10. **Enable Production**: Switch from the n8n Test URL to the Production URL in your Stripe Webhook settings and activate the workflow.

Data Mapping

| Stripe Field | Xero Field | n8n Expression / Strategy | Required | | :--- | :--- | :--- | :--- | | `customer` | `ContactID` | `{{ $node["Xero_Find_Contact"].json["ContactID"] }}` | Yes | | `amount` | `LineItem.UnitAmount` | `{{ $json["amount"] / 100 }}` (Convert cents to units) | Yes | | `id` | `InvoiceNumber` | `STRIPE_{{ $json["id"] }}` (Unique Prefix) | Yes | | `currency` | `CurrencyCode` | `{{ $json["currency"].toUpperCase() }}` | Yes | | `description` | `LineItem.Description` | `{{ $json["description"] || 'Stripe Sale' }}` (Default fallback) | No | | `fee` | `BankTransaction.Amount` | `{{ $node["Get_TX"].json["fee"] / 100 }}` | Yes |

Gotchas & Failure Modes

• **Decimal Precision**: Stripe sends amounts as integers (cents), but Xero expects floats (dollars). Always divide by 100 in your expressions. • **Contact Duplication**: Xero API may return multiple results if not filtered by email. Always use the 'Get All' node with a restricted filter logic rather than assuming an ID exists. • **Stripe Fee Retrieval**: The Stripe Webhook `payment_intent.succeeded` does NOT always include the fee amount. You must use a second Stripe node in n8n to 'Get' the `balance_transaction` using the ID provided in the webhook payload. • **Rate Limits**: Xero has a limit of 10,000 API calls per day and 60 per minute. If processing high-volume Stripe events, use n8n's 'Wait' node or 'Split in Batches' to throttle execution. • **Account Codes**: Hardcode your Xero Chart of Account codes (e.g., '200' for Sales, '400' for Bank Fees) in the n8n node configuration to avoid mapping errors.

Verification Checklist

- [ ] **Check Stripe Webhook**: Ensure the Webhook status in Stripe is '200 OK' for the last test execution. - [ ] **Verify Currency Logic**: Confirm decimal places in Xero (e.g., $10.00 vs $1000) match the expected Stripe payment. - [ ] **Duplicate Test**: Attempt to process the same Stripe Charge ID twice; ensure n8n/Xero correctly handles the unique Reference violation. - [ ] **Fee Reconciliation**: Verify that the 'Spend Money' transaction in Xero matches the 'Fee' column in the Stripe Dashboard. - [ ] **Audit Logs**: Check the n8n 'Execution' tab to ensure all nodes turned green and data passed through the binary/JSON buffers correctly.

Ready to Automate?

Build this automation with n8n in minutes.