This workflow follows the HTTP Request → Supabase 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 →
{
"name": "WF-003 Usage Aggregator",
"nodes": [
{
"parameters": {
"rule": {
"interval": [
{
"field": "cronExpression",
"expression": "0 1 * * *"
}
]
}
},
"id": "wf003-node-schedule",
"name": "Daily 1AM ET",
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1.2,
"position": [
0,
300
]
},
{
"parameters": {
"operation": "getAll",
"tableId": "tenants",
"returnAll": true,
"filterType": "string",
"filterString": "subscription_status=eq.active"
},
"id": "wf003-node-get-tenants",
"name": "Get Active Tenants",
"type": "n8n-nodes-base.supabase",
"typeVersion": 1,
"position": [
250,
300
],
"credentials": {
"supabaseApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"batchSize": 1,
"options": {}
},
"id": "wf003-node-loop",
"name": "Loop Over Tenants",
"type": "n8n-nodes-base.splitInBatches",
"typeVersion": 3,
"position": [
500,
300
]
},
{
"parameters": {
"operation": "getAll",
"tableId": "calls",
"returnAll": true,
"filterType": "string",
"filterString": "={{ 'tenant_id=eq.' + $json.id + '&created_at=gte.' + new Date(Date.now() - 86400000).toISOString().split('T')[0] }}"
},
"id": "wf003-node-get-calls",
"name": "Get Calls (Last 24h)",
"type": "n8n-nodes-base.supabase",
"typeVersion": 1,
"position": [
750,
300
],
"credentials": {
"supabaseApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"jsCode": "// Aggregate call data for the current tenant\nconst items = $input.all();\nconst tenantItem = $('Loop Over Tenants').item;\nconst tenantId = tenantItem.json.id;\n\nconst calls = items.map(i => i.json);\nconst today = new Date();\nconst dateStr = today.toISOString().split('T')[0];\n\nconst totalCalls = calls.length;\nconst totalMinutes = Math.round(\n calls.reduce((sum, c) => sum + (c.duration_seconds || 0), 0) / 60 * 100\n) / 100;\nconst answered = calls.filter(c => c.status === 'answered').length;\nconst missed = calls.filter(c => c.status === 'missed').length;\nconst voicemail = calls.filter(c => c.status === 'voicemail').length;\nconst escalations = calls.filter(c => c.escalated === true).length;\n\nreturn [\n {\n json: {\n date: dateStr,\n tenant_id: tenantId,\n total_calls: totalCalls,\n total_minutes: totalMinutes,\n answered: answered,\n missed: missed,\n voicemail: voicemail,\n escalations: escalations\n }\n }\n];"
},
"id": "wf003-node-aggregate",
"name": "Aggregate Daily Stats",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1000,
300
]
},
{
"parameters": {
"operation": "upsert",
"tableId": "usage_daily",
"conflictColumns": "date,tenant_id",
"columns": "date,tenant_id,total_calls,total_minutes,answered,missed,voicemail,escalations"
},
"id": "wf003-node-upsert-daily",
"name": "Upsert usage_daily",
"type": "n8n-nodes-base.supabase",
"typeVersion": 1,
"position": [
1250,
300
],
"credentials": {
"supabaseApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"jsCode": "// Build monthly aggregation record from daily data\nconst item = $input.all()[0].json;\nconst tenantId = item.tenant_id;\nconst date = new Date(item.date);\nconst yearMonth = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`;\n\nreturn [\n {\n json: {\n year_month: yearMonth,\n tenant_id: tenantId,\n total_calls_increment: item.total_calls,\n total_minutes_increment: item.total_minutes,\n answered_increment: item.answered,\n missed_increment: item.missed,\n voicemail_increment: item.voicemail,\n escalations_increment: item.escalations\n }\n }\n];"
},
"id": "wf003-node-monthly-prep",
"name": "Prepare Monthly Update",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1500,
300
]
},
{
"parameters": {
"method": "POST",
"url": "https://yepggeqhtspgzlkhqhgo.supabase.co/rest/v1/rpc/upsert_usage_monthly",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "supabaseApi",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
},
{
"name": "Prefer",
"value": "return=representation"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ p_year_month: $json.year_month, p_tenant_id: $json.tenant_id, p_total_calls: $json.total_calls_increment, p_total_minutes: $json.total_minutes_increment, p_answered: $json.answered_increment, p_missed: $json.missed_increment, p_voicemail: $json.voicemail_increment, p_escalations: $json.escalations_increment }) }}"
},
"id": "wf003-node-upsert-monthly",
"name": "Upsert usage_monthly",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
1750,
300
],
"credentials": {
"supabaseApi": {
"name": "<your credential>"
}
}
}
],
"connections": {
"Daily 1AM ET": {
"main": [
[
{
"node": "Get Active Tenants",
"type": "main",
"index": 0
}
]
]
},
"Get Active Tenants": {
"main": [
[
{
"node": "Loop Over Tenants",
"type": "main",
"index": 0
}
]
]
},
"Loop Over Tenants": {
"main": [
null,
[
{
"node": "Get Calls (Last 24h)",
"type": "main",
"index": 0
}
]
]
},
"Get Calls (Last 24h)": {
"main": [
[
{
"node": "Aggregate Daily Stats",
"type": "main",
"index": 0
}
]
]
},
"Aggregate Daily Stats": {
"main": [
[
{
"node": "Upsert usage_daily",
"type": "main",
"index": 0
}
]
]
},
"Upsert usage_daily": {
"main": [
[
{
"node": "Prepare Monthly Update",
"type": "main",
"index": 0
}
]
]
},
"Prepare Monthly Update": {
"main": [
[
{
"node": "Upsert usage_monthly",
"type": "main",
"index": 0
}
]
]
},
"Upsert usage_monthly": {
"main": [
[
{
"node": "Loop Over Tenants",
"type": "main",
"index": 0
}
]
]
}
},
"settings": {
"executionOrder": "v1",
"saveManualExecutions": true,
"callerPolicy": "workflowsFromSameOwner",
"timezone": "America/New_York"
},
"meta": {
"templateCredsSetupCompleted": false
},
"active": false,
"tags": [
{
"name": "usage"
},
{
"name": "daily"
},
{
"name": "aggregation"
}
]
}
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.
supabaseApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
WF-003 Usage Aggregator. Uses supabase, httpRequest. Scheduled trigger; 8 nodes.
Source: https://github.com/rafiulislam4246/voice-sdr-title-agent/blob/main/app/n8n-workflows/WF-003-usage-aggregator.json — 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.
WF-005 Overage Warning. Uses httpRequest, emailSend, supabase. Scheduled trigger; 11 nodes.
WF-008 Monthly Report. Uses supabase, httpRequest, emailSend. Scheduled trigger; 11 nodes.
Optimize Campaigns. Uses httpRequest, slack, supabase. Scheduled trigger; 10 nodes.
WF-004 Renewal Reminder. Uses httpRequest, emailSend, supabase. Scheduled trigger; 8 nodes.
LinkedIn_Job_Hunt_and_Cover_Letter. Uses outputParserStructured, outputParserAutofixing, googleDrive, agent. Scheduled trigger; 85 nodes.