This workflow corresponds to n8n.io template #8943 — we link there as the canonical source.
This workflow follows the HTTP Request → Slack recipe pattern — see all workflows that pair these two integrations.
The workflow JSON
Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →
{
"id": "2K34ps0eKEbH4n1w",
"meta": {
"templateCredsSetupCompleted": true
},
"name": "Quaterly/Monthly Revenue Summary To Slack",
"tags": [],
"nodes": [
{
"id": "4ab84e5a-6892-4428-bda6-3a8bdb8a2255",
"name": "Workflow Description",
"type": "n8n-nodes-base.stickyNote",
"position": [
-336,
272
],
"parameters": {
"color": 6,
"width": 310,
"height": 480,
"content": "## \ud83d\udcca Stripe Financial Reporting Workflow\n\nThis workflow automatically generates comprehensive monthly and quarterly financial reports from your Stripe data and sends them to Slack.\n\n**Features:**\n- Monthly reports on the 1st of each month\n- Quarterly reports every 3 months\n- Revenue analysis with refund tracking\n- Customer insights and payment method breakdown\n- Risk analysis and refund reasons\n- Automated Slack notifications\n\n**Perfect for:** Business owners, finance teams, and anyone who needs regular Stripe revenue insights delivered automatically."
},
"typeVersion": 1
},
{
"id": "c7612bb2-02bc-4ca2-b8fe-cd1ae7562c41",
"name": "Setup Instructions",
"type": "n8n-nodes-base.stickyNote",
"position": [
-384,
-96
],
"parameters": {
"color": 7,
"width": 358,
"height": 320,
"content": "## \u2699\ufe0f Setup Instructions\n\n1. **Stripe API Credentials:** Add your Stripe API key in the credential settings\n2. **Slack Integration:** Connect your Slack workspace and select the target channel\n3. **Schedule Timing:** Modify cron expressions if you want different timing\n4. **Channel Configuration:** Update the Slack channel ID to your desired channel\n\n**Important:** Test the workflow manually before enabling the schedule triggers!"
},
"typeVersion": 1
},
{
"id": "29f2e1af-d3a6-4c02-b727-3b0f35084544",
"name": "Monthly Schedule (1st day, 9 AM)",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
128,
128
],
"parameters": {
"rule": {
"interval": [
{
"field": "cronExpression",
"expression": "0 9 1 */1 *"
}
]
}
},
"typeVersion": 1
},
{
"id": "7012ee9d-438f-40e8-9f17-f29be330a3a1",
"name": "Quarterly Schedule (1st day every 3 months, 9 AM)",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
128,
320
],
"parameters": {
"rule": {
"interval": [
{
"field": "cronExpression",
"expression": "0 9 1 */3 *"
}
]
}
},
"typeVersion": 1
},
{
"id": "edac5bee-0e1f-468f-884a-ef99558c12e8",
"name": "Schedule Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
16,
-144
],
"parameters": {
"color": 7,
"width": 342,
"height": 240,
"content": "## \ud83d\uddd3\ufe0f Schedule Configuration\n\n**Monthly:** Runs on the 1st of every month at 9 AM\n**Quarterly:** Runs on the 1st day of every 3rd month at 9 AM\n\nBoth triggers feed into the same date calculation logic to determine the appropriate reporting period."
},
"typeVersion": 1
},
{
"id": "eeef03a3-45f4-4bdd-ab84-0c43e7ad1afa",
"name": "Calculate Date Range",
"type": "n8n-nodes-base.code",
"position": [
352,
224
],
"parameters": {
"jsCode": "// Determine if this is monthly or quarterly based on current date\nconst now = new Date();\nconst currentMonth = now.getMonth() + 1; // getMonth() returns 0-11\n\n// Check if current month is a quarter start (Jan=1, Apr=4, Jul=7, Oct=10)\nconst isQuarterly = currentMonth % 3 === 1;\n\n// Calculate date ranges\nlet startDate, endDate, period;\n\nif (isQuarterly && [1, 4, 7, 10].includes(currentMonth)) {\n // Quarterly report - get previous quarter\n const quarterStartMonth = currentMonth - 3;\n startDate = new Date(now.getFullYear(), quarterStartMonth - 1, 1);\n if (quarterStartMonth <= 0) {\n startDate = new Date(now.getFullYear() - 1, quarterStartMonth + 12 - 1, 1);\n }\n endDate = new Date(now.getFullYear(), currentMonth - 1, 0); // Last day of previous month\n period = 'quarterly';\n} else {\n // Monthly report - get previous month\n startDate = new Date(now.getFullYear(), currentMonth - 2, 1);\n if (currentMonth === 1) {\n startDate = new Date(now.getFullYear() - 1, 11, 1); // December of previous year\n }\n endDate = new Date(now.getFullYear(), currentMonth - 1, 0); // Last day of previous month\n period = 'monthly';\n}\n\n// Convert to Unix timestamps for Stripe API\nconst startTimestamp = Math.floor(startDate.getTime() / 1000);\nconst endTimestamp = Math.floor(endDate.getTime() / 1000);\n\nreturn [{\n json: {\n startDate: startTimestamp,\n endDate: endTimestamp,\n period: period,\n startDateFormatted: startDate.toISOString().split('T')[0],\n endDateFormatted: endDate.toISOString().split('T')[0]\n }\n}];"
},
"typeVersion": 2
},
{
"id": "a66fb369-570e-4494-a2a9-ce2071757e4e",
"name": "Date Range Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
288,
384
],
"parameters": {
"color": 7,
"width": 246,
"height": 288,
"content": "## \ud83d\udcc5 Date Range Logic\n\nThis node determines whether to generate a monthly or quarterly report based on the current date and calculates the appropriate date range for data collection.\n\nOutputs Unix timestamps for Stripe API compatibility."
},
"typeVersion": 1
},
{
"id": "5d537942-36cb-40e5-b20c-d36c26a1c94b",
"name": "Get Stripe Charges",
"type": "n8n-nodes-base.stripe",
"position": [
576,
128
],
"parameters": {
"resource": "charge",
"operation": "getAll",
"returnAll": true
},
"credentials": {
"stripeApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "f6301611-55a1-4598-ba5c-14ece63f4e55",
"name": "Stripe Data Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
464,
-128
],
"parameters": {
"color": 7,
"width": 326,
"height": 240,
"content": "## \ud83d\udcb3 Stripe Data Collection\n\n**Charges Node:** Fetches all successful payments\n**Refunds Node:** Fetches all refund transactions\n\nBoth nodes use your Stripe API credentials and collect data for the calculated date range."
},
"typeVersion": 1
},
{
"id": "d1f56e15-1867-4677-a044-0be5fc745288",
"name": "Format Slack Message",
"type": "n8n-nodes-base.code",
"position": [
1248,
224
],
"parameters": {
"jsCode": "const data = $json;\n\n// Format currency\nconst formatCurrency = (amount) => {\n return new Intl.NumberFormat('en-US', {\n style: 'currency',\n currency: 'USD'\n }).format(amount);\n};\n\n// Format percentage\nconst formatPercentage = (value) => {\n return `${value}%`;\n};\n\n// Create period title from metadata\nconst periodTitle = `Revenue Report - ${data.metadata.period}`;\n\n// Build top customers text\nlet topCustomersText = '';\nif (data.customers.topCustomers && data.customers.topCustomers.length > 0) {\n topCustomersText = data.customers.topCustomers.map((customer, index) => {\n const customerName = customer.customer === 'Unknown Customer' ? \n 'Unknown Customer' : \n `Customer ${customer.customer.substring(4, 14)}...`; // Show more meaningful part of customer ID\n return ` ${index + 1}. ${customerName}: ${formatCurrency(customer.amount)}`;\n }).join('\\n');\n} else {\n topCustomersText = ' No customer data available';\n}\n\n// Build payment methods breakdown\nlet paymentMethodsText = '';\nif (data.payments.paymentMethodBreakdown) {\n paymentMethodsText = Object.entries(data.payments.paymentMethodBreakdown)\n .map(([method, amount]) => ` \u2022 ${method.charAt(0).toUpperCase() + method.slice(1)}: ${formatCurrency(amount)}`)\n .join('\\n');\n}\n\n// Build refund reasons text\nlet refundReasonsText = '';\nif (data.refundAnalysis.refundReasons && Object.keys(data.refundAnalysis.refundReasons).length > 0) {\n refundReasonsText = Object.entries(data.refundAnalysis.refundReasons)\n .map(([reason, count]) => ` \u2022 ${reason.replace(/_/g, ' ').replace(/\\b\\w/g, l => l.toUpperCase())}: ${count}`)\n .join('\\n');\n} else {\n refundReasonsText = ' No refunds in this period';\n}\n\n// Risk level summary\nconst riskSummary = `Low: ${data.payments.riskAnalysis.low} | Medium: ${data.payments.riskAnalysis.medium} | High: ${data.payments.riskAnalysis.high}`;\n\n// Build the comprehensive Slack message\nconst slackMessage = `\ud83d\udcca **${periodTitle}**\n\ud83d\udcc5 *Generated: ${new Date(data.metadata.dataProcessedAt).toLocaleDateString()}*\n\n\ud83d\udcb0 **Financial Summary:**\n\u2022 Total Revenue: ${formatCurrency(data.summary.totalRevenue)}\n\u2022 Total Refunds: ${formatCurrency(data.summary.totalRefunds)}\n\u2022 Net Revenue: ${formatCurrency(data.summary.netRevenue)}\n\u2022 Average Transaction: ${formatCurrency(data.summary.averageTransactionValue)}\n\n\ud83d\udcc8 **Growth Metrics:**\n\u2022 Estimated MRR: ${formatCurrency(data.growth.estimatedMRR)}\n\u2022 Estimated ARR: ${formatCurrency(data.growth.estimatedARR)}\n\n\ud83d\udcca **Transaction Analysis:**\n\u2022 Successful Transactions: ${data.summary.totalTransactions}\n\u2022 Refund Transactions: ${data.summary.totalRefundTransactions}\n\u2022 Refund Rate: ${formatPercentage(data.summary.refundRate)}\n\u2022 Unique Customers: ${data.customers.uniqueCustomers}\n\n\ud83c\udfc6 **Top Customers by Revenue:**\n${topCustomersText}\n\n\ud83d\udcb3 **Payment Methods:**\n${paymentMethodsText}\n\n\u26a0\ufe0f **Risk Analysis:**\n${riskSummary}\n\n\ud83d\udd04 **Refund Analysis:**\n\u2022 Refund Rate: ${formatPercentage(data.refundAnalysis.refundRate)}\n**Refund Reasons:**\n${refundReasonsText}\n\n---\n*Data processed: ${data.metadata.chargesAnalyzed} charges, ${data.metadata.refundsAnalyzed} refunds*\n*Auto-generated from Stripe API*`;\n\nreturn [{\n json: {\n text: slackMessage,\n // Include original data for reference\n originalData: data,\n // Additional Slack formatting options\n blocks: [\n {\n \"type\": \"section\",\n \"text\": {\n \"type\": \"mrkdwn\",\n \"text\": slackMessage\n }\n }\n ]\n }\n}];"
},
"typeVersion": 2
},
{
"id": "ba6697a5-1aac-4b74-859f-6344c20fadfc",
"name": "Slack Format Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
1184,
384
],
"parameters": {
"color": 7,
"width": 278,
"height": 288,
"content": "## \ud83d\udcac Slack Message Formatting\n\nTransforms the financial metrics into a beautifully formatted Slack message with:\n- Emojis and formatting\n- Currency formatting\n- Percentage calculations\n- Organized sections for easy reading"
},
"typeVersion": 1
},
{
"id": "85f38b2b-9e5a-4cb6-a2cf-56687a822544",
"name": "Calculate Financial Metrics",
"type": "n8n-nodes-base.code",
"position": [
1024,
224
],
"parameters": {
"jsCode": "// Get all merged data from the merge node\nconst allData = $input.all();\n\n// Separate charges and refunds based on the data structure\nlet charges = [];\nlet refunds = [];\n\n// Process each item from the merged data\nallData.forEach(item => {\n const data = item.json;\n \n // Check if this is a charge object\n if (data.object === 'charge') {\n charges.push(data);\n }\n // Check if this is refunds list object\n else if (data.object === 'list' && data.data) {\n // Extract refund objects from the list\n refunds = refunds.concat(data.data);\n }\n // Check if this is a single refund object\n else if (data.object === 'refund') {\n refunds.push(data);\n }\n});\n\n// Filter only successful/paid charges that are not refunded\nconst paidCharges = charges.filter(charge => \n charge.status === 'succeeded' && \n charge.paid === true &&\n charge.amount_refunded === 0 // This means not refunded\n);\n\n// Calculate total revenue from non-refunded charges\nconst totalRevenue = paidCharges.reduce((sum, charge) => {\n return sum + (charge.amount / 100); // Convert from cents to dollars\n}, 0);\n\n// Calculate total refunds amount\nconst totalRefundsAmount = refunds.reduce((sum, refund) => {\n return sum + (refund.amount / 100); // Convert from cents to dollars\n}, 0);\n\n// Calculate net revenue\nconst netRevenue = totalRevenue - totalRefundsAmount;\n\n// Get customer revenue breakdown\nconst customerRevenue = {};\npaidCharges.forEach(charge => {\n const customer = charge.customer || 'Unknown Customer';\n const amount = charge.amount / 100;\n customerRevenue[customer] = (customerRevenue[customer] || 0) + amount;\n});\n\n// Get top 3 customers by revenue\nconst topCustomers = Object.entries(customerRevenue)\n .sort(([,a], [,b]) => b - a)\n .slice(0, 3)\n .map(([customer, amount]) => ({ \n customer, \n amount: Math.round(amount * 100) / 100 \n }));\n\n// Calculate payment method breakdown\nconst paymentMethodBreakdown = {};\npaidCharges.forEach(charge => {\n const brand = charge.payment_method_details?.card?.brand || 'unknown';\n const amount = charge.amount / 100;\n paymentMethodBreakdown[brand] = (paymentMethodBreakdown[brand] || 0) + amount;\n});\n\n// Calculate average transaction value\nconst averageTransactionValue = paidCharges.length > 0 ? totalRevenue / paidCharges.length : 0;\n\n// Get current date for period estimation\nconst currentDate = new Date();\nconst currentMonth = currentDate.getMonth() + 1;\nconst currentYear = currentDate.getFullYear();\n\n// Simple MRR estimation (assuming monthly data)\nconst estimatedMRR = totalRevenue;\nconst estimatedARR = estimatedMRR * 12;\n\n// Risk analysis based on risk scores\nconst riskAnalysis = {\n low: paidCharges.filter(c => c.outcome?.risk_score <= 20).length,\n medium: paidCharges.filter(c => c.outcome?.risk_score > 20 && c.outcome?.risk_score <= 50).length,\n high: paidCharges.filter(c => c.outcome?.risk_score > 50).length\n};\n\n// Refund reasons breakdown\nconst refundReasons = {};\nrefunds.forEach(refund => {\n const reason = refund.reason || 'no_reason_given';\n refundReasons[reason] = (refundReasons[reason] || 0) + 1;\n});\n\nreturn [{\n json: {\n // Summary metrics\n summary: {\n totalRevenue: Math.round(totalRevenue * 100) / 100,\n totalRefunds: Math.round(totalRefundsAmount * 100) / 100,\n netRevenue: Math.round(netRevenue * 100) / 100,\n totalTransactions: paidCharges.length,\n totalRefundTransactions: refunds.length,\n averageTransactionValue: Math.round(averageTransactionValue * 100) / 100,\n refundRate: paidCharges.length > 0 ? Math.round((refunds.length / paidCharges.length) * 100 * 100) / 100 : 0\n },\n \n // Growth metrics\n growth: {\n estimatedMRR: Math.round(estimatedMRR * 100) / 100,\n estimatedARR: Math.round(estimatedARR * 100) / 100\n },\n \n // Customer insights\n customers: {\n topCustomers: topCustomers,\n uniqueCustomers: Object.keys(customerRevenue).length\n },\n \n // Payment analysis\n payments: {\n paymentMethodBreakdown: paymentMethodBreakdown,\n riskAnalysis: riskAnalysis\n },\n \n // Refund analysis\n refundAnalysis: {\n refundReasons: refundReasons,\n refundRate: totalRevenue > 0 ? Math.round((totalRefundsAmount / totalRevenue) * 100 * 100) / 100 : 0\n },\n \n // Metadata\n metadata: {\n dataProcessedAt: new Date().toISOString(),\n chargesAnalyzed: charges.length,\n refundsAnalyzed: refunds.length,\n period: `${currentMonth}/${currentYear}`\n }\n }\n}];"
},
"typeVersion": 2
},
{
"id": "7695ff48-1b74-4a29-9d1c-963515e0c7de",
"name": "Financial Metrics Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
960,
-240
],
"parameters": {
"color": 7,
"width": 246,
"height": 432,
"content": "## \ud83d\udcca Financial Analysis Engine\n\nThis node processes the merged Stripe data to calculate:\n- Revenue metrics (total, net, average)\n- Customer insights (top customers, unique count)\n- Payment method breakdown\n- Risk analysis by transaction risk score\n- Refund analysis with reasons\n- Growth metrics (MRR/ARR estimates)\n\nAll amounts are converted from cents to dollars and properly rounded."
},
"typeVersion": 1
},
{
"id": "ce84cff7-e8e7-474a-9c30-e93a032f2074",
"name": "Merge",
"type": "n8n-nodes-base.merge",
"position": [
800,
224
],
"parameters": {},
"typeVersion": 3.2
},
{
"id": "6d4051e6-7972-47c4-99e1-57e109edcbb5",
"name": "Merge Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
752,
384
],
"parameters": {
"color": 7,
"width": 246,
"content": "## \ud83d\udd04 Data Merger\n\nCombines charges and refunds data streams into a single dataset for comprehensive analysis."
},
"typeVersion": 1
},
{
"id": "eb6df223-4d46-47f9-bd11-44a1b16b2c8b",
"name": "Send To Slack",
"type": "n8n-nodes-base.slack",
"position": [
1472,
224
],
"parameters": {
"text": "={{ $json.text }}",
"select": "channel",
"channelId": {
"__rl": true,
"mode": "list",
"value": "C09H21LK9BJ",
"cachedResultName": "reply-needed"
},
"otherOptions": {
"mrkdwn": true
}
},
"credentials": {
"slackApi": {
"name": "<your credential>"
}
},
"typeVersion": 2
},
{
"id": "c57a3d62-a880-46fd-95a6-5d9ea10b5e85",
"name": "Slack Delivery Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
1472,
-64
],
"parameters": {
"color": 7,
"width": 294,
"height": 240,
"content": "## \ud83d\ude80 Slack Delivery\n\n**IMPORTANT:** Update the channelId value to match your target Slack channel.\n\nThe formatted report will be sent with markdown formatting enabled for better readability."
},
"typeVersion": 1
},
{
"id": "25c482b2-ffed-4faf-afd3-5148ded842f1",
"name": "Get Stripe Refunds",
"type": "n8n-nodes-base.httpRequest",
"position": [
576,
320
],
"parameters": {
"url": "https://api.stripe.com/v1/refunds",
"options": {},
"sendQuery": true,
"authentication": "predefinedCredentialType",
"queryParameters": {
"parameters": [
{
"name": "limit",
"value": "100"
}
]
},
"nodeCredentialType": "stripeApi"
},
"credentials": {
"stripeApi": {
"name": "<your credential>"
}
},
"typeVersion": 4.1
}
],
"active": false,
"settings": {
"executionOrder": "v1"
},
"versionId": "7edbbd68-e5ad-4b54-a498-8d1b69da06a2",
"connections": {
"Merge": {
"main": [
[
{
"node": "Calculate Financial Metrics",
"type": "main",
"index": 0
}
]
]
},
"Get Stripe Charges": {
"main": [
[
{
"node": "Merge",
"type": "main",
"index": 0
}
]
]
},
"Get Stripe Refunds": {
"main": [
[
{
"node": "Merge",
"type": "main",
"index": 1
}
]
]
},
"Calculate Date Range": {
"main": [
[
{
"node": "Get Stripe Charges",
"type": "main",
"index": 0
},
{
"node": "Get Stripe Refunds",
"type": "main",
"index": 0
}
]
]
},
"Format Slack Message": {
"main": [
[
{
"node": "Send To Slack",
"type": "main",
"index": 0
}
]
]
},
"Calculate Financial Metrics": {
"main": [
[
{
"node": "Format Slack Message",
"type": "main",
"index": 0
}
]
]
},
"Monthly Schedule (1st day, 9 AM)": {
"main": [
[
{
"node": "Calculate Date Range",
"type": "main",
"index": 0
}
]
]
},
"Quarterly Schedule (1st day every 3 months, 9 AM)": {
"main": [
[
{
"node": "Calculate Date Range",
"type": "main",
"index": 0
}
]
]
}
}
}
Credentials you'll need
Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.
slackApistripeApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Automate your financial reporting by pulling charge and refund data from Stripe, calculating key revenue and risk metrics, and delivering professional reports directly into Slack. This workflow runs on a monthly or quarterly schedule, processes Stripe data into insights, and…
Source: https://n8n.io/workflows/8943/ — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
This workflow is an automated employee time tracking and reporting system that monitors weekly work hours via TMetric, then delivers personalized summaries directly to each team member on Slack. It co
Import Productboard Notes Companies And Features Into Snowflake. Uses stickyNote, httpRequest, splitOut, snowflake. Scheduled trigger; 35 nodes.
Import Productboard Notes, Companies and Features into Snowflake. Uses stickyNote, httpRequest, splitOut, snowflake. Scheduled trigger; 35 nodes.
This workflow imports Productboard data into Snowflake, automating data extraction, mapping, and updates for features, companies, and notes. It supports scheduled weekly updates, data cleansing, and S
This workflow streamlines the entire inventory replenishment process by leveraging AI for demand forecasting and intelligent logic for supplier selection. It aggregates data from multiple sources—POS