This workflow corresponds to n8n.io template #17045 — we link there as the canonical source.
This workflow follows the Google Sheets → HTTP Request 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": "Court Deadline Calculator",
"tags": [],
"nodes": [
{
"id": "cb779c96-0634-4aa5-b97a-26e86b7384fe",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-608,
-400
],
"parameters": {
"width": 480,
"height": 896,
"content": "## Court Deadline Calculator\n\n### How it works\n\nThis workflow receives court deadline calculation requests through a webhook, normalizes and validates the payload, and skips processing when required fields are missing. For valid requests, it calculates deadlines using firm configuration stored in n8n Variables, then creates a calendar event in either Google Calendar or Outlook based on the selected platform. It updates the related matter in Clio and records the result in an audit log spreadsheet.\n\n### Setup steps\n\n- Configure the Deadline Webhook URL and ensure callers send the required deadline request fields expected by the normalization code.\n- Set the firm details and any deadline-calculation settings in n8n project Variables as referenced by the Calculate Deadlines node.\n- Add credentials or authorization headers for Google Calendar and Microsoft Graph/Outlook, depending on which calendar platforms will be used.\n- Configure Clio API credentials for creating or updating matter tasks via the Clio endpoint.\n- Connect the Google Sheets credential and select the spreadsheet/sheet used for the audit log.\n\n### Customization\n\nAdjust the validation and calculation code to match local court rules, filing types, holidays, and firm-specific reminder policies. The calendar branch can be extended for additional calendar platforms, and the audit log fields can be changed to match reporting needs."
},
"typeVersion": 1
},
{
"id": "122feca5-af35-4336-817f-5c5c18eaa5eb",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-48,
-160
],
"parameters": {
"color": 7,
"width": 640,
"height": 320,
"content": "## Receive and validate request\n\nWebhook intake and initial payload preparation. This cluster receives the deadline request, normalizes the incoming data, and checks whether the request has the minimum required fields."
},
"typeVersion": 1
},
{
"id": "77202e37-ba3e-4e5c-9661-0e19361a4e3b",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
624,
-16
],
"parameters": {
"color": 7,
"width": 240,
"height": 384,
"content": "## Handle invalid requests\n\nLower branch for requests that fail validation. The no-op node stops the invalid path without creating deadlines or external records."
},
"typeVersion": 1
},
{
"id": "a3a17526-7d44-48c3-b247-97a706e76675",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
624,
-384
],
"parameters": {
"color": 7,
"width": 416,
"height": 336,
"content": "## Calculate and route deadlines\n\nValid requests continue through deadline computation, using firm details from n8n Variables, then branch according to the selected calendar platform."
},
"typeVersion": 1
},
{
"id": "2a5d66b3-dd82-43c7-a255-3cdb7b17b02d",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
1072,
-400
],
"parameters": {
"color": 7,
"width": 240,
"height": 720,
"content": "## Create calendar events\n\nParallel calendar output cluster. Depending on the platform decision, the workflow posts the calculated deadline to either Google Calendar or Outlook/Microsoft Graph."
},
"typeVersion": 1
},
{
"id": "c0c92d00-a8fd-46a4-a3a0-6413afb8364d",
"name": "Sticky Note5",
"type": "n8n-nodes-base.stickyNote",
"position": [
1344,
-176
],
"parameters": {
"color": 7,
"width": 416,
"height": 336,
"content": "## Update matter and audit\n\nFinal recording cluster. After a calendar event is created, the workflow updates the matter in Clio and logs the completed operation to Google Sheets for auditing."
},
"typeVersion": 1
},
{
"id": "trigger",
"name": "When Deadline Posted",
"type": "n8n-nodes-base.webhook",
"notes": "Receives the deadline calculation request \u2014 from your practice management system, a scheduled cron trigger, or a manual submission form.\n\nCopy the Production URL (visible after you activate the workflow) and paste it into whatever system will trigger deadline calculations for your matters.\n\nExpected payload fields:\n matter_id \u2014 your internal matter reference (e.g. 'M-2026-042')\n clio_matter_id \u2014 Clio's internal numeric matter ID (find it in the Clio URL: /matters/98765)\n matter_title \u2014 the case caption or matter name shown in calendar events\n trigger_date \u2014 the date calculations count from, in YYYY-MM-DD (e.g. the complaint filing date, service date, or scheduling order date)\n attorney_email \u2014 the attorney's email address (informational; used in calendar event metadata)\n calendar_platform \u2014 'google' or 'outlook' (default: google)\n rules \u2014 optional array of deadline rules; if absent, DEFAULT_RULES from Calculate Deadlines are used\n\nThe Read Deadline Request step validates the payload and sets is_valid_request = true only when matter_id and a valid trigger_date are present.",
"position": [
0,
0
],
"parameters": {
"path": "court-deadlines",
"options": {},
"httpMethod": "POST"
},
"typeVersion": 2.1
},
{
"id": "normalize",
"name": "Normalize and Validate Payload",
"type": "n8n-nodes-base.code",
"notes": "Reads and validates the incoming webhook payload. Extracts the fields the rest of the workflow needs and sets a flag indicating whether the request is actionable.\n\nRequired fields: matter_id (any non-empty string) and trigger_date (YYYY-MM-DD format). Everything else is optional.\n\nIf the rules array is absent or empty, the Calculate Deadlines node falls back to its DEFAULT_RULES. This lets you trigger the workflow for every new matter of the same type without passing rules each time \u2014 just configure DEFAULT_RULES once in Calculate Deadlines and the same set applies to every trigger.\n\nOutput: all normalised fields, plus is_valid_request (true/false), validation_error (message or null), and received_at timestamp.",
"position": [
224,
0
],
"parameters": {
"jsCode": "// Normalises the incoming payload and validates the minimum required fields.\n// Sets is_valid_request = true only when matter_id and a valid trigger_date are present.\n\nconst raw = $input.first().json;\nconst body = raw.body || raw;\n\nconst matterId = String(body.matter_id || '').trim();\nconst clioMatterId = body.clio_matter_id != null ? body.clio_matter_id : null;\nconst triggerDate = String(body.trigger_date || '').trim();\nconst matterTitle = String(body.matter_title || body.case_title || 'Matter').trim();\nconst attorneyEmail = String(body.attorney_email || '').trim();\nconst calendarPlatform = String(body.calendar_platform || 'google').toLowerCase().trim();\nconst rules = Array.isArray(body.rules) ? body.rules : [];\n\nconst dateOk =\n /^\\d{4}-\\d{2}-\\d{2}$/.test(triggerDate) &&\n !isNaN(new Date(triggerDate + 'T12:00:00Z').getTime());\n\nconst isValidRequest = !!matterId && dateOk;\n\nreturn [{ json: {\n matter_id: matterId,\n clio_matter_id: clioMatterId,\n trigger_date: triggerDate,\n matter_title: matterTitle,\n attorney_email: attorneyEmail,\n calendar_platform: calendarPlatform,\n rules,\n is_valid_request: isValidRequest,\n validation_error: !matterId ? 'Missing matter_id'\n : !dateOk ? 'Invalid or missing trigger_date \u2014 use YYYY-MM-DD'\n : null,\n received_at: new Date().toISOString(),\n}}];"
},
"typeVersion": 2
},
{
"id": "is_valid",
"name": "If Valid Request",
"type": "n8n-nodes-base.if",
"notes": "Decides whether to proceed or stop before any calculation runs.\n\nYes (matter_id present and trigger_date is a valid date) \u2192 proceed to Calculate Deadlines.\nNo (missing or malformed fields) \u2192 route to Skip \u2014 invalid request. Check the validation_error field in the Read Deadline Request output to see what was missing.",
"onError": "continueErrorOutput",
"position": [
448,
0
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "c_valid",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"leftValue": "={{ $json.is_valid_request }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2.3
},
{
"id": "skip_invalid",
"name": "Ignore Invalid Request",
"type": "n8n-nodes-base.noOp",
"notes": "Safely ends the workflow for incomplete or malformed requests. No deadlines are calculated, no calendar events are created, and no Clio tasks are written.\n\nThis endpoint is visible in the execution history \u2014 check the validation_error field in the Read Deadline Request output to see what was missing from the payload.",
"position": [
672,
200
],
"parameters": {},
"typeVersion": 1
},
{
"id": "calculate",
"name": "Compute Court Deadlines",
"type": "n8n-nodes-base.code",
"notes": "Calculates the calendar date of each court deadline, skipping weekends and US federal holidays for business-day rules.\n\nThe firm owns the rule set \u2014 this workflow never suggests or interprets legal deadlines. Replace the three example placeholder rules with the actual deadline rules your attorney has entered for your practice area and jurisdiction.\n\nReads from n8n Variables (project \u2192 Variables tab):\n FIRM_NAME \u2014 your law firm's name\n FIRM_EMAIL \u2014 your intake email address\n FIRM_TIMEZONE \u2014 your state's timezone (e.g. 'America/Chicago', 'America/Los_Angeles')\n OPS1_SHEET_ID \u2014 the ID from your OPS1 Compliance Log sheet URL (same sheet used by the Bar-Compliance Guardrail): .../spreadsheets/d/SHEET_ID/edit\n\n\u2699\ufe0f Edit DEFAULT_RULES directly in this code node \u2014 add your firm's rules; each rule needs:\n name \u2014 shown in calendar events and Clio tasks\n offset_days \u2014 days from trigger_date (positive = after, negative = before)\n offset_type \u2014 'business' to skip weekends + holidays, 'calendar' to count every day\n description \u2014 longer note shown in calendar event body and Clio task description\n\nThis node outputs one item per deadline rule. Each downstream node (calendar event, Clio task) runs once per deadline \u2014 five rules produce five calendar events and five Clio tasks in a single workflow run.\n\nUS federal holidays handled: New Year's Day, MLK Day, Presidents' Day, Memorial Day, Juneteenth, Independence Day, Labor Day, Columbus Day, Veterans Day, Thanksgiving, Christmas. Saturday holidays observe Friday; Sunday holidays observe Monday.",
"position": [
672,
-208
],
"parameters": {
"jsCode": "// Reads firm details from n8n Variables (project \u2192 Variables tab).\n// Set: FIRM_NAME, FIRM_EMAIL, FIRM_TIMEZONE, OPS1_SHEET_ID\nconst FIRM_NAME = $vars.FIRM_NAME;\nconst FIRM_EMAIL = $vars.FIRM_EMAIL;\nconst TIMEZONE = $vars.FIRM_TIMEZONE; // e.g. 'America/Chicago', 'America/Los_Angeles'\nconst SHEET_ID = $vars.OPS1_SHEET_ID; // same OPS1 Compliance Log sheet used by the Bar-Compliance Guardrail\n\n// \u2699\ufe0f Replace these example placeholders with the actual rules your attorney has entered.\n// This workflow never suggests or interprets legal deadlines \u2014 the firm owns every rule.\n//\n// offset_days: positive = after trigger date, negative = before trigger date\n// offset_type: 'business' = skip weekends + US federal holidays\n// 'calendar' = count every day including weekends and holidays\nconst DEFAULT_RULES = [\n {\n name: 'Example Deadline A \u2014 replace this',\n offset_days: 30,\n offset_type: 'business',\n description: 'Placeholder \u2014 replace with your firm rule (e.g. 30 business days from trigger date per local rules)',\n },\n {\n name: 'Example Deadline B \u2014 replace this',\n offset_days: 60,\n offset_type: 'business',\n description: 'Placeholder \u2014 replace with your firm rule (e.g. 60 business days from trigger date per local rules)',\n },\n {\n name: 'Example Deadline C \u2014 replace this',\n offset_days: 14,\n offset_type: 'calendar',\n description: 'Placeholder \u2014 replace with your firm rule (e.g. 14 calendar days from trigger date per local rules)',\n },\n];\n\n// \u2500\u2500 US Federal Holiday calculator \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nfunction nthWeekday(year, month0, weekday, n) {\n if (n > 0) {\n const d = new Date(Date.UTC(year, month0, 1));\n let count = 0;\n while (true) {\n if (d.getUTCDay() === weekday) { count++; if (count === n) return new Date(d); }\n d.setUTCDate(d.getUTCDate() + 1);\n }\n } else {\n const d = new Date(Date.UTC(year, month0 + 1, 0));\n while (true) {\n if (d.getUTCDay() === weekday) return new Date(d);\n d.setUTCDate(d.getUTCDate() - 1);\n }\n }\n}\n\nfunction getUSFederalHolidays(year) {\n const set = new Set();\n function addObserved(date) {\n const d = new Date(date);\n const day = d.getUTCDay();\n if (day === 6) d.setUTCDate(d.getUTCDate() - 1); // Saturday \u2192 observe Friday\n if (day === 0) d.setUTCDate(d.getUTCDate() + 1); // Sunday \u2192 observe Monday\n set.add(d.toISOString().slice(0, 10));\n }\n addObserved(new Date(Date.UTC(year, 0, 1))); // New Year's Day\n addObserved(nthWeekday(year, 0, 1, 3)); // MLK Day (3rd Mon Jan)\n addObserved(nthWeekday(year, 1, 1, 3)); // Presidents' Day (3rd Mon Feb)\n addObserved(nthWeekday(year, 4, 1, -1)); // Memorial Day (last Mon May)\n addObserved(new Date(Date.UTC(year, 5, 19))); // Juneteenth\n addObserved(new Date(Date.UTC(year, 6, 4))); // Independence Day\n addObserved(nthWeekday(year, 8, 1, 1)); // Labor Day (1st Mon Sep)\n addObserved(nthWeekday(year, 9, 1, 2)); // Columbus Day (2nd Mon Oct)\n addObserved(new Date(Date.UTC(year, 10, 11))); // Veterans Day\n addObserved(nthWeekday(year, 10, 4, 4)); // Thanksgiving (4th Thu Nov)\n addObserved(new Date(Date.UTC(year, 11, 25))); // Christmas\n return set;\n}\n\nconst holidayCache = {};\n\nfunction isBusinessDay(dateStr) {\n const d = new Date(dateStr + 'T12:00:00Z');\n const dow = d.getUTCDay();\n if (dow === 0 || dow === 6) return false;\n const yr = d.getUTCFullYear();\n if (!holidayCache[yr]) holidayCache[yr] = getUSFederalHolidays(yr);\n return !holidayCache[yr].has(dateStr);\n}\n\nfunction addBusinessDays(startDateStr, n) {\n const direction = n >= 0 ? 1 : -1;\n const steps = Math.abs(n);\n const d = new Date(startDateStr + 'T12:00:00Z');\n let counted = 0;\n while (counted < steps) {\n d.setUTCDate(d.getUTCDate() + direction);\n if (isBusinessDay(d.toISOString().slice(0, 10))) counted++;\n }\n return d.toISOString().slice(0, 10);\n}\n\nfunction addCalendarDays(startDateStr, n) {\n const d = new Date(startDateStr + 'T12:00:00Z');\n d.setUTCDate(d.getUTCDate() + n);\n return d.toISOString().slice(0, 10);\n}\n\n// \u2500\u2500 Main \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst item = $input.first().json;\nconst rules = (item.rules && item.rules.length > 0) ? item.rules : DEFAULT_RULES;\n\nconst deadlines = rules.map(rule => {\n const offsetDays = Number(rule.offset_days) || 0;\n const offsetType = (rule.offset_type || 'business').toLowerCase();\n const deadlineDate = offsetType === 'calendar'\n ? addCalendarDays(item.trigger_date, offsetDays)\n : addBusinessDays(item.trigger_date, offsetDays);\n\n return {\n matter_id: item.matter_id,\n clio_matter_id: item.clio_matter_id,\n matter_title: item.matter_title,\n attorney_email: item.attorney_email,\n calendar_platform: item.calendar_platform,\n rule_name: rule.name,\n deadline_date: deadlineDate,\n trigger_date: item.trigger_date,\n offset_days: offsetDays,\n offset_type: offsetType,\n description: rule.description || rule.name,\n firm_name: FIRM_NAME,\n firm_email: FIRM_EMAIL,\n timezone: TIMEZONE,\n sheet_id: SHEET_ID,\n };\n});\n\nreturn deadlines.map(d => ({ json: d }));"
},
"typeVersion": 2
},
{
"id": "platform_if",
"name": "Determine Calendar Platform",
"type": "n8n-nodes-base.if",
"notes": "Routes each deadline to the correct calendar service based on the calendar_platform value in the request.\n\nGoogle Calendar (true branch) \u2192 Create Google Calendar Event\nMicrosoft Outlook (false branch) \u2192 Create Outlook Calendar Event\n\nYou only need to wire credentials for the calendar your firm uses. Leave the unused branch's credential blank \u2014 that branch never executes.\n\nTo default to Outlook, change the condition value from 'google' to 'outlook' and swap the branch labels.",
"onError": "continueErrorOutput",
"position": [
896,
-208
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": false,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "c_platform",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.calendar_platform }}",
"rightValue": "google"
}
]
}
},
"typeVersion": 2.3
},
{
"id": "google_cal",
"name": "Post to Google Calendar API",
"type": "n8n-nodes-base.httpRequest",
"notes": "Creates an all-day calendar event in Google Calendar for each calculated deadline. This node runs once per deadline rule \u2014 five rules produce five calendar events.\n\nCredential to wire: In n8n go to project \u2192 Credentials tab \u2192 New, search for 'Google Calendar OAuth2 API', and connect your Google account. Save it as 'Google Calendar account'.\n\nCalendar: events are created on the attorney's primary Google Calendar. To write to a shared firm calendar instead, replace 'primary' in the URL with the calendar's ID \u2014 find it in Google Calendar \u2192 Settings \u2192 the calendar \u2192 Calendar ID (e.g. 'intake@smithlaw.com').\n\nEvent title format: 'Rule Name \u2014 Matter Title'\nEvent body: rule description + matter ID + trigger date\n\nIf this node fails (e.g. credential not connected), the workflow continues to Update Matter in Clio \u2014 deadlines are still written to Clio even if the calendar step does not complete.",
"onError": "continueRegularOutput",
"position": [
1120,
-160
],
"parameters": {
"url": "https://www.googleapis.com/calendar/v3/calendars/primary/events",
"method": "POST",
"options": {
"response": {
"response": {
"neverError": true,
"responseFormat": "json"
}
}
},
"jsonBody": "={\n \"summary\": {{ JSON.stringify($json.rule_name + ' \u2014 ' + $json.matter_title) }},\n \"description\": {{ JSON.stringify($json.description + '\\nMatter: ' + $json.matter_id + '\\nTrigger date: ' + $json.trigger_date) }},\n \"start\": { \"date\": {{ JSON.stringify($json.deadline_date) }} },\n \"end\": { \"date\": {{ JSON.stringify($json.deadline_date) }} }\n}",
"sendBody": true,
"sendHeaders": true,
"specifyBody": "json",
"authentication": "predefinedCredentialType",
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"nodeCredentialType": "googleCalendarOAuth2Api"
},
"credentials": {
"googleCalendarOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.4
},
{
"id": "outlook_cal",
"name": "Post to Outlook Calendar API",
"type": "n8n-nodes-base.httpRequest",
"notes": "Creates a calendar event in Microsoft Outlook via the Microsoft Graph API for each calculated deadline. This node runs once per deadline rule.\n\nCredential to wire: In n8n go to project \u2192 Credentials tab \u2192 New, search for 'Microsoft Outlook OAuth2 API', and connect your Microsoft 365 account. Save it as 'Outlook account'.\n\nCalendar: events are created in the attorney's primary Outlook calendar at 9:00\u20139:30 AM on the deadline date. To write to a shared calendar, replace 'me' in the URL with the shared mailbox address: https://graph.microsoft.com/v1.0/users/shared@firm.com/events\n\nTimezone: reads from the FIRM_OUTLOOK_TIMEZONE n8n Variable (project \u2192 Variables tab). Microsoft Outlook requires Windows timezone format \u2014 valid values include 'Eastern Standard Time', 'Central Standard Time', 'Mountain Standard Time', 'Pacific Standard Time'.\n\nIf this node fails, the workflow continues to Update Matter in Clio so deadlines are still recorded there.",
"onError": "continueRegularOutput",
"position": [
1120,
160
],
"parameters": {
"url": "https://graph.microsoft.com/v1.0/me/events",
"method": "POST",
"options": {
"response": {
"response": {
"neverError": true,
"responseFormat": "json"
}
}
},
"jsonBody": "={\n \"subject\": {{ JSON.stringify($json.rule_name + ' \u2014 ' + $json.matter_title) }},\n \"body\": {\n \"contentType\": \"text\",\n \"content\": {{ JSON.stringify($json.description + '\\nMatter: ' + $json.matter_id + '\\nTrigger date: ' + $json.trigger_date) }}\n },\n \"start\": { \"dateTime\": {{ JSON.stringify($json.deadline_date + 'T09:00:00') }}, \"timeZone\": {{ JSON.stringify($vars.FIRM_OUTLOOK_TIMEZONE) }} },\n \"end\": { \"dateTime\": {{ JSON.stringify($json.deadline_date + 'T09:30:00') }}, \"timeZone\": {{ JSON.stringify($vars.FIRM_OUTLOOK_TIMEZONE) }} }\n}",
"sendBody": true,
"sendHeaders": true,
"specifyBody": "json",
"authentication": "predefinedCredentialType",
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"nodeCredentialType": "microsoftOutlookOAuth2Api"
},
"credentials": {
"microsoftOutlookOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.4
},
{
"id": "clio_task",
"name": "Update Clio Task API",
"type": "n8n-nodes-base.httpRequest",
"notes": "Creates a high-priority task in Clio for each calculated deadline, linked to the matter. This node runs once per deadline rule \u2014 five rules produce five Clio tasks in a single run.\n\nCredential to wire:\n1. In Clio go to Settings \u2192 API Keys \u2192 New API key and copy the token\n2. In n8n go to project \u2192 Credentials tab \u2192 New, search for 'Header Auth'\n3. Set Name field to 'Authorization' and Value field to 'Bearer YOUR_TOKEN' (include 'Bearer ' with a space)\n4. Save it as 'Clio API token'\n\nClio matter ID: the task is linked to the matter via clio_matter_id in your webhook payload. This must be Clio's internal numeric ID \u2014 find it in the URL when viewing the matter in Clio (/matters/98765). If you pass display numbers like 'M-2026-042' instead of the numeric ID, Clio will reject the request. The task is still created; link it to the matter manually inside Clio.\n\nTask properties: name = rule name, due date = calculated deadline date, priority = high, status = pending.\n\nFor the full Clio REST API reference see: developer.clio.com",
"onError": "continueRegularOutput",
"position": [
1392,
0
],
"parameters": {
"url": "https://app.clio.com/api/v4/tasks.json",
"body": "={{ JSON.stringify({ data: { name: $('Compute Court Deadlines').item.json.rule_name, due_at: $('Compute Court Deadlines').item.json.deadline_date + 'T00:00:00.000Z', description: $('Compute Court Deadlines').item.json.description, matter: { id: $('Compute Court Deadlines').item.json.clio_matter_id }, assignee: { type: 'User' }, status: 'pending', priority: 'high' } }) }}",
"method": "POST",
"options": {
"response": {
"response": {
"neverError": true,
"responseFormat": "json"
}
}
},
"sendBody": true,
"sendHeaders": true,
"specifyBody": "string",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
}
]
}
},
"credentials": {
"httpHeaderAuth": {
"name": "<your credential>"
}
},
"typeVersion": 4.4
},
{
"id": "audit_log",
"name": "Append Audit Log in Sheets",
"type": "n8n-nodes-base.googleSheets",
"notes": "Appends one audit row per deadline to the Audit Log tab of your OPS1 Compliance Log sheet. Creates a permanent record of every deadline calculated \u2014 matter ID, rule name, trigger date, calculated deadline date, and calendar platform used.\n\nCredential to wire: project \u2192 Credentials tab \u2192 New \u2192 search 'Google Sheets OAuth2' \u2192 connect with the Google account that owns the OPS1 Compliance Log sheet \u2192 save as 'Google Sheets account'.\n\nSheet ID: set SHEET_ID in the Calculate Deadlines node to the ID from your compliance sheet URL: .../spreadsheets/d/SHEET_ID/edit. This is the same sheet used by the Bar-Compliance Guardrail \u2014 use the same ID.\n\nIf this step fails (e.g., credentials not wired), the workflow continues \u2014 deadlines are still written to Clio and your calendar. A logging failure never blocks deadline creation.",
"onError": "continueRegularOutput",
"position": [
1616,
0
],
"parameters": {
"columns": {
"value": {
"status": "calculated",
"channel": "={{ 'calendar-' + $('Compute Court Deadlines').item.json.calendar_platform }}",
"logged_at": "={{ new Date().toISOString() }}",
"matter_id": "={{ $('Compute Court Deadlines').item.json.matter_id }}",
"recipient": "={{ $('Compute Court Deadlines').item.json.attorney_email }}",
"template_id": "NTC-16",
"message_preview": "={{ ($('Compute Court Deadlines').item.json.rule_name + ' \u2192 ' + $('Compute Court Deadlines').item.json.deadline_date + ' (trigger: ' + $('Compute Court Deadlines').item.json.trigger_date + ')').substring(0, 200) }}"
},
"schema": [],
"mappingMode": "defineBelow",
"matchingColumns": []
},
"operation": "append",
"sheetName": {
"__rl": true,
"mode": "name",
"value": "Audit Log"
},
"documentId": {
"__rl": true,
"mode": "id",
"value": "={{ $vars.OPS1_SHEET_ID }}"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4
}
],
"active": false,
"settings": {
"availableInMCP": false,
"executionOrder": "v1",
"executionTimeout": -1,
"saveManualExecutions": true,
"saveDataErrorExecution": "all",
"saveDataSuccessExecution": "all"
},
"nodeGroups": [],
"staticData": null,
"connections": {
"If Valid Request": {
"main": [
[
{
"node": "Compute Court Deadlines",
"type": "main",
"index": 0
}
],
[
{
"node": "Ignore Invalid Request",
"type": "main",
"index": 0
}
]
]
},
"Update Clio Task API": {
"main": [
[
{
"node": "Append Audit Log in Sheets",
"type": "main",
"index": 0
}
]
]
},
"When Deadline Posted": {
"main": [
[
{
"node": "Normalize and Validate Payload",
"type": "main",
"index": 0
}
]
]
},
"Compute Court Deadlines": {
"main": [
[
{
"node": "Determine Calendar Platform",
"type": "main",
"index": 0
}
]
]
},
"Determine Calendar Platform": {
"main": [
[
{
"node": "Post to Google Calendar API",
"type": "main",
"index": 0
}
],
[
{
"node": "Post to Outlook Calendar API",
"type": "main",
"index": 0
}
]
]
},
"Post to Google Calendar API": {
"main": [
[
{
"node": "Update Clio Task API",
"type": "main",
"index": 0
}
]
]
},
"Post to Outlook Calendar API": {
"main": [
[
{
"node": "Update Clio Task API",
"type": "main",
"index": 0
}
]
]
},
"Normalize and Validate Payload": {
"main": [
[
{
"node": "If Valid Request",
"type": "main",
"index": 0
}
]
]
}
},
"description": "Receives a trigger date and rule set via webhook, calculates court deadlines using business-day math (skipping weekends and US federal holidays), creates an all-day calendar event per deadline in Google Calendar or Outlook, and writes each deadline back as a high-priority task in Clio. Replaces DocketCalendar and LawToolBox ($49\u2013$149/user/mo) and guards against the missed-deadline risk that drives 24.6% of malpractice claims. The firm owns and confirms every rule \u2014 this workflow never suggests or interprets legal deadlines."
}
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.
googleCalendarOAuth2ApigoogleSheetsOAuth2ApihttpHeaderAuthmicrosoftOutlookOAuth2Api
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This workflow receives a webhook with a trigger date and deadline rules, calculates court deadlines (skipping weekends and US federal holidays for business-day rules), then creates matching events in Google Calendar or Microsoft Outlook, creates high-priority tasks in Clio, logs…
Source: https://n8n.io/workflows/17045/ — 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.
Automate WhatsApp communication for recruitment agencies with an interactive, structured customer experience. This workflow handles pricing inquiries, request submissions, tracking, complaints, and hu
This workflow automates HR onboarding by capturing new hires via a webhook, saving them to Google Sheets, emailing a Slack invite via Gmail, and notifying managers in Slack, then listening for Slack t
Hectelion | Evaluation d'entreprise. Uses googleDrive, httpRequest, microsoftOutlook, googleSheets. Webhook trigger; 64 nodes.
Advanced AI Powered Document Parsing & Text Extraction with Llama Parse. Uses gmail, gmailTrigger, httpRequest, googleSheets. Webhook trigger; 54 nodes.
Hectelion | NDA. Uses googleSheets, googleDrive, httpRequest, microsoftOutlook. Webhook trigger; 50 nodes.