Consolidate Stripe, PayPal, Shopify and bank revenue and prepare tax filings with OpenAI
Pull contacts, verify each address with BillionVerify, and continue to PayPal — only deliverable addresses get through.
Why verify before the send
Sending to invalid, risky, catch-all, or disposable addresses spikes your bounce rate and erodes sender reputation. A verification gate before the PayPal step removes that risk automatically — only deliverable addresses continue, the rest are flagged.
The workflow
BillionVerify — verification sits right before the send.
Node by node
- 1OpenAI ModelSource· n8n
Provides or transforms the contact data flowing through the workflow.
- 2Structured Output ParserSource· n8n
Provides or transforms the contact data flowing through the workflow.
- 3Monthly revenue aggregationTrigger· n8n
Starts the workflow — on a schedule, a webhook, or manually while you test.
- 4Workflow ConfigurationSource· n8n
Provides or transforms the contact data flowing through the workflow.
- 5Get Stripe TransactionsSource· n8n
Provides or transforms the contact data flowing through the workflow.
- 6Get PayPal TransactionsSource· n8n
Provides or transforms the contact data flowing through the workflow.
- 7Get Shopify OrdersSource· n8n
Provides or transforms the contact data flowing through the workflow.
- 8Get Bank Feed DataSource· n8n
Provides or transforms the contact data flowing through the workflow.
- 9Normalize Stripe DataSource· n8n
Provides or transforms the contact data flowing through the workflow.
- 10Normalize PayPal DataSource· n8n
Provides or transforms the contact data flowing through the workflow.
- 11Normalize Shopify DataSource· n8n
Provides or transforms the contact data flowing through the workflow.
- 12Normalize Bank DataSource· n8n
Provides or transforms the contact data flowing through the workflow.
- 13Merge All Revenue SourcesSource· n8n
Provides or transforms the contact data flowing through the workflow.
- 14AI Income CategorizerSource· n8n
Provides or transforms the contact data flowing through the workflow.
- 15Calculate Period TotalsSource· n8n
Provides or transforms the contact data flowing through the workflow.
- 16Format as CSVSource· n8n
Provides or transforms the contact data flowing through the workflow.
- 17Format as XMLSource· n8n
Provides or transforms the contact data flowing through the workflow.
- 18Check Submission MethodLogic· n8n
Branches on the verification result: only deliverable addresses continue to the send; the rest are skipped and flagged.
- 19Archive to Google DriveSource· n8n
Provides or transforms the contact data flowing through the workflow.
- 20Submit to Government APISource· n8n
Provides or transforms the contact data flowing through the workflow.
- 21Verify Email (BillionVerify)Verify· billionverify
The BillionVerify node verifies the address — status (valid / invalid / risky / catch-all / role / disposable), is_deliverable, and a confidence score — before anything is sent.
- 22IF deliverableLogic· n8n
Branches on the verification result: only deliverable addresses continue to the send; the rest are skipped and flagged.
- 23Email to Tax AgentSend· n8n
Sends only to verified, deliverable addresses. Swap in your own provider node if you send elsewhere.
Workflow JSON
Copy or download this workflow, then import it in n8n (Workflows → Import from File / Paste). Install the BillionVerify community node first, then add your API key credential.
{
"name": "Consolidate Stripe, PayPal, Shopify and bank revenue and prepare tax filings with OpenAI + BillionVerify",
"nodes": [
{
"id": "827cbd50-730e-4ddf-93dc-fa68d0724ff9",
"name": "Workflow Configuration",
"type": "n8n-nodes-base.set",
"position": [
-224,
288
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "id-1",
"name": "taxPeriodStart",
"type": "string",
"value": "={{ $now.minus({ months: 1 }).startOf('month').toISO() }}"
},
{
"id": "id-2",
"name": "taxPeriodEnd",
"type": "string",
"value": "={{ $now.minus({ months: 1 }).endOf('month').toISO() }}"
},
{
"id": "id-3",
"name": "submissionMethod",
"type": "string",
"value": "api"
},
{
"id": "id-4",
"name": "taxAgentEmail",
"type": "string",
"value": "<__PLACEHOLDER_VALUE__Tax Agent Email Address__>"
},
{
"id": "id-5",
"name": "governmentApiUrl",
"type": "string",
"value": "<__PLACEHOLDER_VALUE__Government Tax API Endpoint__>"
},
{
"id": "id-6",
"name": "bankFeedApiUrl",
"type": "string",
"value": "<__PLACEHOLDER_VALUE__Bank Feed API Endpoint__>"
}
]
},
"includeOtherFields": true
},
"typeVersion": 3.4
},
{
"id": "74d8059b-52fe-4b35-bc6a-c89a90803868",
"name": "Get Stripe Transactions",
"type": "n8n-nodes-base.stripe",
"position": [
0,
80
],
"parameters": {
"limit": 100,
"resource": "charge",
"operation": "getAll"
},
"typeVersion": 1
},
{
"id": "48dce256-d478-4814-a1c3-48b0087f57e9",
"name": "Get PayPal Transactions",
"type": "n8n-nodes-base.payPal",
"position": [
0,
272
],
"parameters": {
"resource": "payoutItem",
"payoutItemId": "<__PLACEHOLDER_VALUE__Payout Item ID__>"
},
"typeVersion": 1
},
{
"id": "475587a0-60f5-45c5-b7dc-e8c5c28d613d",
"name": "Get Shopify Orders",
"type": "n8n-nodes-base.shopify",
"position": [
0,
464
],
"parameters": {
"options": {
"createdAtMax": "={{ $('Workflow Configuration').first().json.taxPeriodEnd }}",
"createdAtMin": "={{ $('Workflow Configuration').first().json.taxPeriodStart }}"
},
"operation": "getAll",
"returnAll": true
},
"typeVersion": 1
},
{
"id": "ba27bdb1-ee18-4fd0-878b-8d19a0fc9db6",
"name": "Get Bank Feed Data",
"type": "n8n-nodes-base.httpRequest",
"position": [
0,
656
],
"parameters": {
"url": "={{ $('Workflow Configuration').first().json.bankFeedApiUrl }}",
"options": {},
"sendQuery": true,
"queryParameters": {
"parameters": [
{
"name": "start_date",
"value": "={{ $('Workflow Configuration').first().json.taxPeriodStart }}"
},
{
"name": "end_date",
"value": "={{ $('Workflow Configuration').first().json.taxPeriodEnd }}"
}
]
}
},
"typeVersion": 4.3
},
{
"id": "627e15cc-7a89-4d09-9f13-bfc21360c1ca",
"name": "Normalize Stripe Data",
"type": "n8n-nodes-base.set",
"position": [
208,
112
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "id-1",
"name": "source",
"type": "string",
"value": "Stripe"
},
{
"id": "id-2",
"name": "transactionId",
"type": "string",
"value": "={{ $json.id }}"
},
{
"id": "id-3",
"name": "amount",
"type": "number",
"value": "={{ $json.amount / 100 }}"
},
{
"id": "id-4",
"name": "currency",
"type": "string",
"value": "={{ $json.currency }}"
},
{
"id": "id-5",
"name": "date",
"type": "string",
"value": "={{ new Date($json.created * 1000).toISOString() }}"
},
{
"id": "id-6",
"name": "description",
"type": "string",
"value": "={{ $json.description }}"
},
{
"id": "id-7",
"name": "customerEmail",
"type": "string",
"value": "={{ $json.billing_details?.email }}"
}
]
},
"includeOtherFields": true
},
"typeVersion": 3.4
},
{
"id": "69974097-d415-4b94-93bb-2ffa7796026e",
"name": "Normalize PayPal Data",
"type": "n8n-nodes-base.set",
"position": [
224,
272
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "id-1",
"name": "source",
"type": "string",
"value": "PayPal"
},
{
"id": "id-2",
"name": "transactionId",
"type": "string",
"value": "={{ $json.payout_item_id }}"
},
{
"id": "id-3",
"name": "amount",
"type": "number",
"value": "={{ $json.payout_item.amount.value }}"
},
{
"id": "id-4",
"name": "currency",
"type": "string",
"value": "={{ $json.payout_item.amount.currency }}"
},
{
"id": "id-5",
"name": "date",
"type": "string",
"value": "={{ $json.time_processed }}"
},
{
"id": "id-6",
"name": "description",
"type": "string",
"value": "={{ $json.payout_item.transaction_note }}"
}
]
},
"includeOtherFields": true
},
"typeVersion": 3.4
},
{
"id": "7a796a3c-a049-48e7-b7b9-ce5cab1a9dd3",
"name": "Normalize Shopify Data",
"type": "n8n-nodes-base.set",
"position": [
224,
464
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "id-1",
"name": "source",
"type": "string",
"value": "Shopify"
},
{
"id": "id-2",
"name": "transactionId",
"type": "string",
"value": "={{ $json.id }}"
},
{
"id": "id-3",
"name": "amount",
"type": "number",
"value": "={{ $json.total_price }}"
},
{
"id": "id-4",
"name": "currency",
"type": "string",
"value": "={{ $json.currency }}"
},
{
"id": "id-5",
"name": "date",
"type": "string",
"value": "={{ $json.created_at }}"
},
{
"id": "id-6",
"name": "description",
"type": "string",
"value": "={{ $json.name }}"
},
{
"id": "id-7",
"name": "customerEmail",
"type": "string",
"value": "={{ $json.email }}"
}
]
},
"includeOtherFields": true
},
"typeVersion": 3.4
},
{
"id": "9e2c41c2-5690-4081-8594-8832a3f4eef1",
"name": "Normalize Bank Data",
"type": "n8n-nodes-base.set",
"position": [
224,
656
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "id-1",
"name": "source",
"type": "string",
"value": "Bank"
},
{
"id": "id-2",
"name": "transactionId",
"type": "string",
"value": "={{ $json.transaction_id }}"
},
{
"id": "id-3",
"name": "amount",
"type": "number",
"value": "={{ $json.amount }}"
},
{
"id": "id-4",
"name": "currency",
"type": "string",
"value": "={{ $json.currency }}"
},
{
"id": "id-5",
"name": "date",
"type": "string",
"value": "={{ $json.date }}"
},
{
"id": "id-6",
"name": "description",
"type": "string",
"value": "={{ $json.description }}"
}
]
},
"includeOtherFields": true
},
"typeVersion": 3.4
},
{
"id": "2a4f11e4-4d51-4c71-baf6-ff241c193e3d",
"name": "Merge All Revenue Sources",
"type": "n8n-nodes-base.aggregate",
"position": [
448,
288
],
"parameters": {
"options": {},
"aggregate": "aggregateAllItemData",
"destinationFieldName": "allTransactions"
},
"typeVersion": 1
},
{
"id": "833c661f-0aff-4be4-834a-108c40f92725",
"name": "AI Income Categorizer",
"type": "@n8n/n8n-nodes-langchain.agent",
"position": [
672,
288
],
"parameters": {
"text": "={{ 'Categorize these revenue transactions: ' + JSON.stringify($json.allTransactions) }}",
"options": {
"systemMessage": "You are a tax categorization assistant. Your task is to analyze revenue transactions and categorize each one according to standard income categories.\n\nFor each transaction, determine:\n1. incomeCategory: The type of income (e.g., \"Product Sales\", \"Service Revenue\", \"Subscription Revenue\", \"Interest Income\", \"Other Income\")\n2. taxable: Whether the income is taxable (true/false)\n3. taxRate: The applicable tax rate as a decimal (e.g., 0.10 for 10%)\n4. notes: Any relevant notes about the categorization\n\nReturn the categorized transactions in the exact structure defined by the output parser."
},
"promptType": "define",
"hasOutputParser": true
},
"typeVersion": 3
},
{
"id": "9497a3e4-6beb-4aff-9032-a765685fc98a",
"name": "OpenAI Model",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"position": [
688,
512
],
"parameters": {
"model": {
"__rl": true,
"mode": "list",
"value": "gpt-4.1-mini"
},
"options": {},
"builtInTools": {}
},
"credentials": {
"openAiApi": {
"id": "mv2ECvRtbAK63G2g",
"name": "OpenAi account"
}
},
"typeVersion": 1.3
},
{
"id": "07444727-4739-44ed-8312-b47e24da62d9",
"name": "Structured Output Parser",
"type": "@n8n/n8n-nodes-langchain.outputParserStructured",
"position": [
816,
512
],
"parameters": {
"schemaType": "manual",
"inputSchema": "{\n \"type\": \"object\",\n \"properties\": {\n \"transactions\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"properties\": {\n \"transactionId\": {\n \"type\": \"string\"\n },\n \"source\": {\n \"type\": \"string\"\n },\n \"amount\": {\n \"type\": \"number\"\n },\n \"currency\": {\n \"type\": \"string\"\n },\n \"date\": {\n \"type\": \"string\"\n },\n \"description\": {\n \"type\": \"string\"\n },\n \"incomeCategory\": {\n \"type\": \"string\"\n },\n \"taxable\": {\n \"type\": \"boolean\"\n },\n \"taxRate\": {\n \"type\": \"number\"\n },\n \"notes\": {\n \"type\": \"string\"\n }\n }\n }\n }\n }\n}"
},
"typeVersion": 1.3
},
{
"id": "d531269c-0591-4ba6-af34-e68613c12329",
"name": "Calculate Period Totals",
"type": "n8n-nodes-base.code",
"position": [
1024,
288
],
"parameters": {
"jsCode": "// Calculate Period Totals for Tax Filing\n// This code processes categorized income data and calculates totals by category\n\nconst items = $input.all();\n\n// Initialize totals object\nconst totals = {\n byCategory: {},\n totalRevenue: 0,\n totalTaxableAmount: 0,\n totalTax: 0,\n transactionCount: 0,\n periodStart: null,\n periodEnd: null\n};\n\n// Process each transaction\nfor (const item of items) {\n const data = item.json;\n \n // Extract relevant fields\n const category = data.category || 'Uncategorized';\n const amount = parseFloat(data.amount) || 0;\n const taxableAmount = parseFloat(data.taxableAmount) || amount;\n const taxRate = parseFloat(data.taxRate) || 0;\n const tax = taxableAmount * (taxRate / 100);\n const date = data.date || data.transactionDate;\n \n // Update category totals\n if (!totals.byCategory[category]) {\n totals.byCategory[category] = {\n revenue: 0,\n taxableAmount: 0,\n tax: 0,\n count: 0\n };\n }\n \n totals.byCategory[category].revenue += amount;\n totals.byCategory[category].taxableAmount += taxableAmount;\n totals.byCategory[category].tax += tax;\n totals.byCategory[category].count += 1;\n \n // Update overall totals\n totals.totalRevenue += amount;\n totals.totalTaxableAmount += taxableAmount;\n totals.totalTax += tax;\n totals.transactionCount += 1;\n \n // Track period dates\n if (date) {\n const transactionDate = new Date(date);\n if (!totals.periodStart || transactionDate < totals.periodStart) {\n totals.periodStart = transactionDate;\n }\n if (!totals.periodEnd || transactionDate > totals.periodEnd) {\n totals.periodEnd = transactionDate;\n }\n }\n}\n\n// Format dates\nif (totals.periodStart) {\n totals.periodStart = totals.periodStart.toISOString().split('T')[0];\n}\nif (totals.periodEnd) {\n totals.periodEnd = totals.periodEnd.toISOString().split('T')[0];\n}\n\n// Round all monetary values to 2 decimal places\ntotals.totalRevenue = Math.round(totals.totalRevenue * 100) / 100;\ntotals.totalTaxableAmount = Math.round(totals.totalTaxableAmount * 100) / 100;\ntotals.totalTax = Math.round(totals.totalTax * 100) / 100;\n\nfor (const category in totals.byCategory) {\n totals.byCategory[category].revenue = Math.round(totals.byCategory[category].revenue * 100) / 100;\n totals.byCategory[category].taxableAmount = Math.round(totals.byCategory[category].taxableAmount * 100) / 100;\n totals.byCategory[category].tax = Math.round(totals.byCategory[category].tax * 100) / 100;\n}\n\n// Create summary output\nconst summary = {\n summary: totals,\n categories: Object.keys(totals.byCategory).map(category => ({\n category: category,\n ...totals.byCategory[category]\n })),\n transactions: items.map(item => item.json)\n};\n\nreturn [summary];"
},
"typeVersion": 2
},
{
"id": "d739f8aa-6436-4d5d-9b54-b33c39eb5b4e",
"name": "Format as CSV",
"type": "n8n-nodes-base.code",
"position": [
1248,
192
],
"parameters": {
"jsCode": "// Convert categorized transaction data and totals into CSV format\nconst items = $input.all();\n\n// Define CSV headers\nconst headers = [\n 'Transaction ID',\n 'Source',\n 'Date',\n 'Amount',\n 'Currency',\n 'Category',\n 'Taxable',\n 'Tax Rate',\n 'Tax Amount',\n 'Description'\n];\n\n// Create CSV rows\nconst rows = items.map(item => {\n const data = item.json;\n return [\n data.transactionId || data.transaction_id || '',\n data.source || '',\n data.date || '',\n data.amount || '',\n data.currency || '',\n data.category || '',\n data.taxable || '',\n data.taxRate || data.tax_rate || '',\n data.taxAmount || data.tax_amount || '',\n data.description || ''\n ];\n});\n\n// Escape CSV fields (handle commas, quotes, newlines)\nconst escapeCSVField = (field) => {\n const fieldStr = String(field);\n if (fieldStr.includes(',') || fieldStr.includes('\"') || fieldStr.includes('\\n')) {\n return '\"' + fieldStr.replace(/\"/g, '\"\"') + '\"';\n }\n return fieldStr;\n};\n\n// Build CSV content\nconst csvRows = [\n headers.map(escapeCSVField).join(','),\n ...rows.map(row => row.map(escapeCSVField).join(','))\n];\n\nconst csvContent = csvRows.join('\\n');\n\n// Return CSV as output\nreturn [\n {\n json: {\n csv: csvContent,\n format: 'csv',\n rowCount: rows.length,\n generatedAt: new Date().toISOString()\n },\n binary: {\n data: {\n data: Buffer.from(csvContent).toString('base64'),\n mimeType: 'text/csv',\n fileName: `revenue_report_${new Date().toISOString().split('T')[0]}.csv`\n }\n }\n }\n];"
},
"typeVersion": 2
},
{
"id": "d22f7d8e-0094-4dfb-b29e-c2d28da87040",
"name": "Format as XML",
"type": "n8n-nodes-base.code",
"position": [
1696,
480
],
"parameters": {
"jsCode": "// Convert categorized transaction data and totals into XML format for government tax filing\nconst items = $input.all();\n\n// Helper function to escape XML special characters\nfunction escapeXml(unsafe) {\n if (unsafe === null || unsafe === undefined) return '';\n return String(unsafe)\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n}\n\n// Helper function to format date to ISO format\nfunction formatDate(date) {\n if (!date) return new Date().toISOString().split('T')[0];\n return new Date(date).toISOString().split('T')[0];\n}\n\n// Build XML structure\nlet xml = '<?xml version=\"1.0\" encoding=\"UTF-8\"?>\\n';\nxml += '<TaxFilingReport>\\n';\nxml += ' <Header>\\n';\nxml += ` <SubmissionDate>${formatDate(new Date())}</SubmissionDate>\\n`;\nxml += ` <TaxPeriod>${items[0]?.json?.taxPeriod || 'QUARTERLY'}</TaxPeriod>\\n`;\nxml += ` <FiscalYear>${new Date().getFullYear()}</FiscalYear>\\n`;\nxml += ` <TaxpayerID>${items[0]?.json?.taxpayerId || 'TBD'}</TaxpayerID>\\n`;\nxml += ' </Header>\\n';\n\n// Add summary totals\nxml += ' <Summary>\\n';\nconst totals = items[0]?.json?.totals || {};\nxml += ` <TotalRevenue>${totals.totalRevenue || 0}</TotalRevenue>\\n`;\nxml += ` <TotalTaxableIncome>${totals.totalTaxableIncome || 0}</TotalTaxableIncome>\\n`;\nxml += ` <TotalDeductions>${totals.totalDeductions || 0}</TotalDeductions>\\n`;\nxml += ` <NetTaxableAmount>${totals.netTaxableAmount || 0}</NetTaxableAmount>\\n`;\nxml += ` <TransactionCount>${items.length}</TransactionCount>\\n`;\nxml += ' </Summary>\\n';\n\n// Add category breakdown\nxml += ' <CategoryBreakdown>\\n';\nconst categories = items[0]?.json?.categoryTotals || {};\nfor (const [category, amount] of Object.entries(categories)) {\n xml += ' <Category>\\n';\n xml += ` <Name>${escapeXml(category)}</Name>\\n`;\n xml += ` <Amount>${amount}</Amount>\\n`;\n xml += ' </Category>\\n';\n}\nxml += ' </CategoryBreakdown>\\n';\n\n// Add individual transactions\nxml += ' <Transactions>\\n';\nfor (const item of items) {\n const data = item.json;\n xml += ' <Transaction>\\n';\n xml += ` <TransactionID>${escapeXml(data.transactionId || data.id)}</TransactionID>\\n`;\n xml += ` <Date>${formatDate(data.date || data.transactionDate)}</Date>\\n`;\n xml += ` <Source>${escapeXml(data.source || 'UNKNOWN')}</Source>\\n`;\n xml += ` <Amount>${data.amount || 0}</Amount>\\n`;\n xml += ` <Currency>${escapeXml(data.currency || 'USD')}</Currency>\\n`;\n xml += ` <Category>${escapeXml(data.category || 'UNCATEGORIZED')}</Category>\\n`;\n xml += ` <Description>${escapeXml(data.description || '')}</Description>\\n`;\n xml += ` <TaxStatus>${escapeXml(data.taxStatus || 'TAXABLE')}</TaxStatus>\\n`;\n if (data.customerName) {\n xml += ` <CustomerName>${escapeXml(data.customerName)}</CustomerName>\\n`;\n }\n xml += ' </Transaction>\\n';\n}\nxml += ' </Transactions>\\n';\n\nxml += '</TaxFilingReport>';\n\n// Return the XML as output\nreturn [{\n json: {\n xml: xml,\n format: 'XML',\n generatedAt: new Date().toISOString(),\n recordCount: items.length\n }\n}];"
},
"typeVersion": 2
},
{
"id": "62bba3e6-8d63-4684-a961-d6b783aea349",
"name": "Check Submission Method",
"type": "n8n-nodes-base.if",
"position": [
1472,
192
],
"parameters": {
"options": {},
"conditions": {
"options": {
"leftValue": "",
"caseSensitive": false,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "id-1",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $('Workflow Configuration').first().json.submissionMethod }}",
"rightValue": "api"
}
]
}
},
"typeVersion": 2.3
},
{
"id": "35e46f44-2e36-4fb6-927d-c4981615836b",
"name": "Submit to Government API",
"type": "n8n-nodes-base.httpRequest",
"position": [
1696,
96
],
"parameters": {
"url": "={{ $('Workflow Configuration').first().json.governmentApiUrl }}",
"body": "={{ $json.csvData }}",
"method": "POST",
"options": {},
"sendBody": true,
"contentType": "raw",
"sendHeaders": true,
"rawContentType": "text/csv",
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "text/csv"
}
]
}
},
"typeVersion": 4.3
},
{
"id": "46772d2d-fb8f-47bb-8943-ef32f2bed8ab",
"name": "Email to Tax Agent",
"type": "n8n-nodes-base.gmail",
"position": [
1696,
288
],
"webhookId": "103d107e-b3c2-4f50-b9a8-84fbb984c6e0",
"parameters": {
"sendTo": "={{ $('Workflow Configuration').first().json.taxAgentEmail }}",
"message": "={{ 'Dear Tax Agent,<br><br>Please find attached the tax filing submission for the period ' + $('Workflow Configuration').first().json.taxPeriodStart + ' to ' + $('Workflow Configuration').first().json.taxPeriodEnd + '.<br><br>Revenue Summary:<br>' + JSON.stringify($('Calculate Period Totals').first().json, null, 2).replace(/\\n/g, '<br>') + '<br><br>The detailed CSV file is attached.<br><br>Best regards,<br>Automated Tax Filing System' }}",
"options": {
"attachmentsUi": {
"attachmentsBinary": [
{
"property": "csvData"
}
]
}
},
"subject": "={{ 'Tax Filing Submission - Period: ' + $('Workflow Configuration').first().json.taxPeriodStart + ' to ' + $('Workflow Configuration').first().json.taxPeriodEnd }}"
},
"credentials": {
"gmailOAuth2": {
"id": "u1N5nBDvQ0AWhNnV",
"name": "Gmail account"
}
},
"typeVersion": 2.2
},
{
"id": "82060b39-886f-4e46-96ad-5f837116f1eb",
"name": "Archive to Google Drive",
"type": "n8n-nodes-base.googleDrive",
"position": [
1920,
288
],
"parameters": {
"name": "={{ 'tax_filing_' + new Date().toISOString().split('T')[0] + '.csv' }}",
"driveId": {
"__rl": true,
"mode": "list",
"value": "My Drive"
},
"options": {
"simplifyOutput": true
},
"folderId": {
"__rl": true,
"mode": "list",
"value": "root",
"cachedResultName": "/ (Root folder)"
}
},
"credentials": {
"googleDriveOAuth2Api": {
"id": "ALHOS4YihnICuh2c",
"name": "Google Drive account"
}
},
"typeVersion": 3
},
{
"id": "43b5a026-1918-4f2b-a76c-3271ba1fd3d4",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
1136,
-288
],
"parameters": {
"color": 5,
"width": 400,
"height": 208,
"content": "## Customization\nModify normalization rules per jurisdiction; add expense categories to AI prompt; \n\n## Benefits\nEliminates manual reconciliation; reduces tax filing time by 80%; improves accuracy; "
},
"typeVersion": 1
},
{
"id": "25215ef2-8cb0-41e3-8f17-706714474149",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
672,
-288
],
"parameters": {
"color": 4,
"width": 416,
"height": 208,
"content": "## Prerequisites\nStripe, PayPal, Shopify, or bank APIs; OpenAI account; Google Workspace; \n\n## Use Cases\nQuarterly tax preparation for e-commerce; multi-channel revenue reconciliation; "
},
"typeVersion": 1
},
{
"id": "219f5590-aeb3-44ac-b607-0ff38da38c07",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
272,
-288
],
"parameters": {
"width": 320,
"height": 208,
"content": "## Setup Steps\n1. Connect Stripe/PayPal/Shopify accounts with API keys to respective nodes.\n2. Configure bank feed authentication \n3. Set OpenAI credentials for AI Income Categorizer node.\n4. Link Google Drive and Gmail .\n"
},
"typeVersion": 1
},
{
"id": "fc759cae-6766-43e5-b517-e03f9bffb698",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
-512,
-288
],
"parameters": {
"width": 752,
"height": 192,
"content": "## How It Works\nConsolidates daily revenue from Stripe, PayPal, Shopify, and bank feeds into a single system. The workflow automatically normalizes data across payment sources, uses AI to categorize income transactions, calculates reporting-period totals, and generates tax-compliant CSV and XML submissions. Designed for e-commerce businesses, SaaS companies, and multi-channel retailers managing complex revenue streams, it eliminates manual reconciliation, reduces filing errors, and speeds up financial reporting by automating the entire pipeline from data collection to government submission."
},
"typeVersion": 1
},
{
"id": "89107252-fe33-4736-aa8f-f364ec4a1f86",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
656,
-32
],
"parameters": {
"color": 7,
"width": 304,
"height": 880,
"content": "## AI-Powered Income Categorization\n**What:** Use all normalized streams and categorizes income using AI-powered transaction analysis.\n**Why:** Automates income classification and detects anomalies "
},
"typeVersion": 1
},
{
"id": "bc7e0768-29f3-4c48-930d-f68f583ba464",
"name": "Sticky Note5",
"type": "n8n-nodes-base.stickyNote",
"position": [
976,
-32
],
"parameters": {
"color": 7,
"width": 1104,
"height": 864,
"content": "## Tax Period Calculation and submission\n**What:** Computes period totals and tax period summaries with validation checks.\n**Why:** Ensures accuracy and compliance-ready figures for regulatory submission."
},
"typeVersion": 1
},
{
"id": "666e7b09-aefd-4026-a92b-3fd11ce34648",
"name": "Sticky Note6",
"type": "n8n-nodes-base.stickyNote",
"position": [
144,
-32
],
"parameters": {
"color": 7,
"width": 496,
"height": 864,
"content": "\n## Data Normalization\n**What:** Applies format-standardization and transformation rules to each data source independently.\n**Why:** Creates consistent data structure "
},
"typeVersion": 1
},
{
"id": "7ea72134-db7a-490c-9bad-91f93cdebb1e",
"name": "Sticky Note7",
"type": "n8n-nodes-base.stickyNote",
"position": [
-512,
-32
],
"parameters": {
"color": 7,
"width": 640,
"height": 848,
"content": "## Multi-Source Data Collection\n**What:** Retrieves raw transaction data from four distinct payment sources \n**Why:** Ensures complete revenue visibility across all channels."
},
"typeVersion": 1
},
{
"id": "a6403c5a-d2fa-45ba-9e73-96d5ee5374fe",
"name": "Monthly revenue aggregation",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
-448,
288
],
"parameters": {
"rule": {
"interval": [
{
"triggerAtHour": 2
}
]
}
},
"typeVersion": 1.3
},
{
"parameters": {
"operation": "verify",
"email": "={{ $('Workflow Configuration').first().json.taxAgentEmail }}",
"additionalOptions": {}
},
"type": "n8n-nodes-billionverify.billionVerify",
"typeVersion": 1,
"position": [
1336,
288
],
"name": "Verify Email (BillionVerify)",
"credentials": {
"billionVerifyApi": {
"id": "",
"name": "BillionVerify account"
}
}
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "is-deliverable",
"leftValue": "={{ $json.is_deliverable }}",
"rightValue": "",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
}
}
]
}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [
1516,
288
],
"name": "IF deliverable"
}
],
"connections": {
"OpenAI Model": {
"ai_languageModel": [
[
{
"node": "AI Income Categorizer",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Format as CSV": {
"main": [
[
{
"node": "Check Submission Method",
"type": "main",
"index": 0
}
]
]
},
"Format as XML": {
"main": [
[
{
"node": "Archive to Google Drive",
"type": "main",
"index": 0
}
]
]
},
"Email to Tax Agent": {
"main": [
[
{
"node": "Archive to Google Drive",
"type": "main",
"index": 0
}
]
]
},
"Get Bank Feed Data": {
"main": [
[
{
"node": "Normalize Bank Data",
"type": "main",
"index": 0
}
]
]
},
"Get Shopify Orders": {
"main": [
[
{
"node": "Normalize Shopify Data",
"type": "main",
"index": 0
}
]
]
},
"Normalize Bank Data": {
"main": [
[
{
"node": "Merge All Revenue Sources",
"type": "main",
"index": 0
}
]
]
},
"AI Income Categorizer": {
"main": [
[
{
"node": "Calculate Period Totals",
"type": "main",
"index": 0
}
]
]
},
"Normalize PayPal Data": {
"main": [
[
{
"node": "Merge All Revenue Sources",
"type": "main",
"index": 0
}
]
]
},
"Normalize Stripe Data": {
"main": [
[
{
"node": "Merge All Revenue Sources",
"type": "main",
"index": 0
}
]
]
},
"Normalize Shopify Data": {
"main": [
[
{
"node": "Merge All Revenue Sources",
"type": "main",
"index": 0
}
]
]
},
"Workflow Configuration": {
"main": [
[
{
"node": "Get Stripe Transactions",
"type": "main",
"index": 0
},
{
"node": "Get PayPal Transactions",
"type": "main",
"index": 0
},
{
"node": "Get Shopify Orders",
"type": "main",
"index": 0
},
{
"node": "Get Bank Feed Data",
"type": "main",
"index": 0
}
]
]
},
"Calculate Period Totals": {
"main": [
[
{
"node": "Format as CSV",
"type": "main",
"index": 0
},
{
"node": "Format as XML",
"type": "main",
"index": 0
}
]
]
},
"Check Submission Method": {
"main": [
[
{
"node": "Submit to Government API",
"type": "main",
"index": 0
}
],
[
{
"node": "Verify Email (BillionVerify)",
"type": "main",
"index": 0
}
]
]
},
"Get PayPal Transactions": {
"main": [
[
{
"node": "Normalize PayPal Data",
"type": "main",
"index": 0
}
]
]
},
"Get Stripe Transactions": {
"main": [
[
{
"node": "Normalize Stripe Data",
"type": "main",
"index": 0
}
]
]
},
"Structured Output Parser": {
"ai_outputParser": [
[
{
"node": "AI Income Categorizer",
"type": "ai_outputParser",
"index": 0
}
]
]
},
"Submit to Government API": {
"main": [
[
{
"node": "Archive to Google Drive",
"type": "main",
"index": 0
}
]
]
},
"Merge All Revenue Sources": {
"main": [
[
{
"node": "AI Income Categorizer",
"type": "main",
"index": 0
}
]
]
},
"Monthly revenue aggregation": {
"main": [
[
{
"node": "Workflow Configuration",
"type": "main",
"index": 0
}
]
]
},
"Verify Email (BillionVerify)": {
"main": [
[
{
"node": "IF deliverable",
"type": "main",
"index": 0
}
]
]
},
"IF deliverable": {
"main": [
[
{
"node": "Email to Tax Agent",
"type": "main",
"index": 0
}
],
[]
]
}
},
"settings": {
"executionOrder": "v1"
}
}When to use this
- Cleaning a list before a PayPal send or sync.
- Protecting PayPal deliverability and sender reputation.
- Keeping bounce rates low so your sending is never throttled.
FAQ
Why verify before sending in PayPal?
Verifying first keeps your bounce rate low, which protects your sender reputation and your results.
How do I import this workflow?
Download the JSON, then in n8n go to Workflows → Import from File (or paste it). Install the BillionVerify community node and add your API key credential.
What happens to risky or catch-all addresses?
They are routed to the false branch and excluded from the send. You decide whether to retry, review, or drop them.
Add verification to your workflow
Create a free account, grab your API key, and stop bounces before they happen.
Get started free