← All N8N workflows
Messaging & emailAggregateCodeCompare DatasetsCryptoExecute Workflow

Realtime Notion Todoist 2-way Sync Template

Configured with Aggregate, Compare Datasets, Crypto, Execute Workflow, and additional nodes. Review the connections and setup before adapting this template.

Workflow preview
Web / APIGet projectsHTTP RequestWeb / APIGet sectionsHTTP RequestIntegrationGet Notion DatabasesNotionCodePrep DropdownCodeCodePrep Dropdown1CodeCodeGenerate configCodeIntegrationChoose Notion DatabaseFormIntegrationChoose Todoist ProjectFormRoutingVerify security tokenIfTransformGenerate security tokenCryptoCodeStore variablesCodeCodeGet variablesCodeIntegrationRedirect to Auth PageFormTriggerOAuth redirectWebhookWeb / APIExchange TokensHTTP RequestWeb / APIRespond with successRespond To WebhookWeb / APIRespond with errorRespond To WebhookCodeGet Notion Database IDCodeCodeGet Todoist Project IDCodeTriggerNotion-Todoist SyncSetup HelperForm TriggerTriggerTodoist Webhook SetupHelperForm TriggerIntegrationReturn config JSONFormIntegrationTodoistTodoistIntegrationNotionNotionTransformCompare DatasetsCompare DatasetsRoutingExists/Completed inNotionIfIntegrationGet Todoist TaskTodoistRoutingIf Todoist ID existsIfIntegrationStore Todoist IDNotionIntegrationMark as Incomplete inTodoistTodoistIntegrationMark as Completed inTodoistTodoistIntegrationDelete Task in TodoistTodoistRoutingLoop Over ItemsLoop Over ItemsIntegrationGet Notion TaskNotionWeb / APIUpdate Task in TodoistHTTP RequestTransformPick Todoist FieldsEdit FieldsCodeMap Notion to TodoistCodeWeb / APIUpdate task in Todoistbefore closingHTTP RequestCodeMap Notion to Todoist1CodeCodeHandle empty datesCodeCodeHandle empty dates1CodeCodeHandle empty dates2CodeWeb / APICreate task in TodoistHTTP RequestTransformAdd project IDEdit FieldsData storeLock Todoist IDRedisTransformGenerate UUIDCryptoWeb / APIUpdate section (SyncAPI)HTTP RequestRoutingStatus changedFilterTransformAggregateAggregateTransformPrepare Batching ItemsEdit FieldsRoutingLoop Over Items1Loop Over ItemsRoutingStatus is DoneIfData storeLock Todoist ID1RedisData storeLock Todoist ID2RedisWeb / APIUpdate task in TodoistHTTP RequestTransformGet mapped itemEdit FieldsTransformGet mapped item1Edit FieldsTriggerSchedule TriggerSchedule TriggerTransformPrepare summary dataEdit FieldsTransformPrepare summary data1Edit FieldsTransformMergeMergeTransformPrepare summary data2Edit FieldsRoutingFilter out statuschangesFilterCodeMap Todoist to NotionCodeTransformMap summary fieldsEdit FieldsTransformConvert to HTML TableHtmlTransformGenerate email bodyHtmlTransformGet mapped item2Edit FieldsMessagingGmailGmailTransformMerge summaryMergeTransformGlobalsEdit FieldsTriggerExecute WorkflowTriggerExecute Workflow TriggerRoutingDifferences exist1FilterRoutingRequires completionchange1FilterIntegrationCreate task in NotionNotionIntegrationUpdate task in NotionNotionIntegrationMark as Done in NotionNotionIntegrationMark as Obsolete inNotionNotionIntegrationMark as In Progress inNotionNotionRoutingOnly continue if notlockedFilterData storeLock Notion IDRedisData storeCheck if Todoist ID islockedRedisIntegrationUpdate Description inTodoistTodoistRoutingDescription has changedin TodoistFilterIntegrationUpdate Description inTodoist1TodoistTransformGenerate UUID1CryptoWeb / APIUpdate section (SyncAPI)1HTTP RequestCodeGet Backlog Section IDCodeRoutingNotion Task not foundIfData storeCheck if creating flagexists1RedisRoutingOnly continue if flagdoes not exist1FilterData storeSet creating flag1RedisRoutingNeither focussed norplanned1FilterIntegrationMark as Focussed inNotionNotionRoutingTodoist triggerreferenceNo OpIntegrationGet Notion Task2NotionRoutingNotion Task found1FilterRoutingSwitch by EventSwitchRoutingIf event is not:deletedIfIntegrationExecution DataExecution DataIntegrationExecution Data1Execution DataIntegrationExecution Data2Execution DataIntegrationExecution Data3Execution DataIntegrationExecution Data4Execution DataTransformGlobals1Edit FieldsCodeMap Todoist to Notion1CodeIntegrationGet Todoist Task1TodoistIntegrationGet Notion Task1NotionRoutingRequires completionchangeFilterRoutingHas been completed?IfRoutingDifferences existFilterRoutingOnly continue if notlocked1FilterData storeCheck if Notion ID islockedRedisIntegrationGet Notion taskNotionData storeCheck if creating flagexistsRedisRoutingOnly continue if flagdoes not existFilterData storeSet creating flagRedisRoutingTodoist ID exists?IfTransformGet todoist IDEdit FieldsRoutingTodoist ID existsFilterTransformGet todoist ID1Edit FieldsRoutingStatus changed but notemptyFilterRoutingStatus is Obsolete?IfRoutingStatus is not DoneFilterRoutingNeither focussed norplannedIfRoutingNotion triggerreferenceNo OpTriggerNotion WebhookWebhookIntegrationStore Todoist ID1NotionIntegrationMark as Completed inTodoist1TodoistWeb / APIUpdate task in Todoist1HTTP RequestTransformGlobals2Edit FieldsIntegrationMark as Incomplete inTodoist1TodoistTransformGenerate UUID2CryptoWeb / APIUpdate section (SyncAPI)2HTTP RequestData storeLock Todoist ID4RedisIntegrationDelete Task in Todoist2TodoistCodeMap Notion to Todoist2CodeIntegrationGet Todoist Task2TodoistWeb / APICreate task in Todoist1HTTP RequestIntegrationExecution Data5Execution DataIntegrationExecution Data6Execution DataTriggerTodoist WebhookWebhookSubworkflowExecute WorkflowExecute WorkflowTransformExtract IDsEdit FieldsTransformSplit out NotionchangesSplit OutRoutingBody is array?IfRoutingSwitch by projectSwitchTriggerWebhookWebhookRoutingSwitchSwitchSubworkflowExecute Workflow1Execute WorkflowRoutingDue date emptyFilterRoutingHas not been completed?IfRoutingEnd hereNo OpRoutingRetry limit reachedStop And ErrorTransformSet triesEdit FieldsTransformUpdate triesEdit FieldsWaitWaitWaitRoutingCatch known errorIfRoutingIf tries leftIfRoutingRetry limit reached1Stop And ErrorTransformSet tries1Edit FieldsTransformUpdate tries1Edit FieldsWaitWait1WaitRoutingCatch known error1IfRoutingIf tries left1IfTransformSet tries2Edit FieldsTransformUpdate tries2Edit FieldsWaitWait2WaitRoutingCatch known error2IfRoutingIf tries left2IfRoutingRetry limit reached2Stop And ErrorCodeTurn Markdown intoNotion BlocksCodeTransformHandle each blockseparatelySplit OutIntegrationAppend Notion BlockNotion

Node categories in this workflow

  • Web / API
  • Integration
  • Code
  • Routing
  • Transform
  • Trigger
  • Data store
  • Messaging
  • Subworkflow
  • Wait

Icons describe general node roles, inferred from node types. Inspect the parameters for the configured action.

Drag to pan; use + and − to zoom. Reconstructed from the exported node positions; nothing runs here. Dashed lines represent non-main connections. Full node names and notes appear below.

How it works

The exported workflow contains the following configured nodes. Their connections are shown above; inspect the JSON for the complete logic, expressions, and external actions.

Workflow node names, types, and operations
NodeCategoryTypeOperation
Get projectsWeb / APIHTTP RequestSee node parameters
Get sectionsWeb / APIHTTP RequestSee node parameters
Get Notion DatabasesIntegrationNotiondatabase / getAll
Prep DropdownCodeCodeSee node parameters
Prep Dropdown1CodeCodeSee node parameters
Generate configCodeCodeSee node parameters
Choose Notion DatabaseIntegrationFormSee node parameters
Choose Todoist ProjectIntegrationFormSee node parameters
Verify security tokenRoutingIfSee node parameters
Generate security tokenTransformCryptoSee node parameters
Store variablesCodeCodeSee node parameters
Get variablesCodeCodeSee node parameters
Redirect to Auth PageIntegrationFormcompletion
OAuth redirectTriggerWebhookSee node parameters
Exchange TokensWeb / APIHTTP RequestPOST
Respond with successWeb / APIRespond To WebhookSee node parameters
Respond with errorWeb / APIRespond To WebhookSee node parameters
Get Notion Database IDCodeCodeSee node parameters
Get Todoist Project IDCodeCodeSee node parameters
Notion-Todoist Sync Setup HelperTriggerForm TriggerSee node parameters
Todoist Webhook Setup HelperTriggerForm TriggerSee node parameters
Return config JSONIntegrationFormcompletion
TodoistIntegrationTodoistgetAll
NotionIntegrationNotiondatabasePage / getAll
Compare DatasetsTransformCompare DatasetsSee node parameters
Exists/Completed in NotionRoutingIfSee node parameters
Get Todoist TaskIntegrationTodoistget
If Todoist ID existsRoutingIfSee node parameters
Store Todoist IDIntegrationNotiondatabasePage / update
Mark as Incomplete in TodoistIntegrationTodoistreopen
Mark as Completed in TodoistIntegrationTodoistclose
Delete Task in TodoistIntegrationTodoistdelete
Loop Over ItemsRoutingLoop Over ItemsSee node parameters
Get Notion TaskIntegrationNotiondatabasePage / getAll
Update Task in TodoistWeb / APIHTTP RequestPOST
Pick Todoist FieldsTransformEdit FieldsSee node parameters
Map Notion to TodoistCodeCodeSee node parameters
Update task in Todoist before closingWeb / APIHTTP RequestPOST
Map Notion to Todoist1CodeCodeSee node parameters
Handle empty datesCodeCodeSee node parameters
Handle empty dates1CodeCodeSee node parameters
Handle empty dates2CodeCodeSee node parameters
Create task in TodoistWeb / APIHTTP RequestPOST
Add project IDTransformEdit FieldsSee node parameters
Lock Todoist IDData storeRedisset
Generate UUIDTransformCryptoSee node parameters
Update section (Sync API)Web / APIHTTP RequestPOST
Status changedRoutingFilterSee node parameters
AggregateTransformAggregateSee node parameters
Prepare Batching ItemsTransformEdit FieldsSee node parameters
Loop Over Items1RoutingLoop Over ItemsSee node parameters
Status is DoneRoutingIfSee node parameters
Lock Todoist ID1Data storeRedisset
Lock Todoist ID2Data storeRedisset
Update task in TodoistWeb / APIHTTP RequestPOST
Get mapped itemTransformEdit FieldsSee node parameters
Get mapped item1TransformEdit FieldsSee node parameters
Schedule TriggerTriggerSchedule TriggerSee node parameters
Prepare summary dataTransformEdit FieldsSee node parameters
Prepare summary data1TransformEdit FieldsSee node parameters
MergeTransformMergeSee node parameters
Prepare summary data2TransformEdit FieldsSee node parameters
Filter out status changesRoutingFilterSee node parameters
Map Todoist to NotionCodeCodeSee node parameters
Map summary fieldsTransformEdit FieldsSee node parameters
Convert to HTML TableTransformHtmlconvertToHtmlTable
Generate email bodyTransformHtmlSee node parameters
Get mapped item2TransformEdit FieldsSee node parameters
GmailMessagingGmailSee node parameters
Merge summaryTransformMergeSee node parameters
GlobalsTransformEdit FieldsSee node parameters
Execute Workflow TriggerTriggerExecute Workflow TriggerSee node parameters
Differences exist1RoutingFilterSee node parameters
Requires completion change1RoutingFilterSee node parameters
Create task in NotionIntegrationNotiondatabasePage
Update task in NotionIntegrationNotiondatabasePage / update
Mark as Done in NotionIntegrationNotiondatabasePage / update
Mark as Obsolete in NotionIntegrationNotiondatabasePage / update
Mark as In Progress in NotionIntegrationNotiondatabasePage / update
Only continue if not lockedRoutingFilterSee node parameters
Lock Notion IDData storeRedisset
Check if Todoist ID is lockedData storeRedisget
Update Description in TodoistIntegrationTodoistupdate
Description has changed in TodoistRoutingFilterSee node parameters
Update Description in Todoist1IntegrationTodoistupdate
Generate UUID1TransformCryptoSee node parameters
Update section (Sync API)1Web / APIHTTP RequestPOST
Get Backlog Section IDCodeCodeSee node parameters
Notion Task not foundRoutingIfSee node parameters
Check if creating flag exists1Data storeRedisget
Only continue if flag does not exist1RoutingFilterSee node parameters
Set creating flag1Data storeRedisset
Neither focussed nor planned1RoutingFilterSee node parameters
Mark as Focussed in NotionIntegrationNotiondatabasePage / update
Todoist trigger referenceRoutingNo OpSee node parameters
Get Notion Task2IntegrationNotiondatabasePage / getAll
Notion Task found1RoutingFilterSee node parameters
Switch by EventRoutingSwitchSee node parameters
If event is not :deletedRoutingIfSee node parameters
Execution DataIntegrationExecution DataSee node parameters
Execution Data1IntegrationExecution DataSee node parameters
Execution Data2IntegrationExecution DataSee node parameters
Execution Data3IntegrationExecution DataSee node parameters
Execution Data4IntegrationExecution DataSee node parameters
Globals1TransformEdit FieldsSee node parameters
Map Todoist to Notion1CodeCodeSee node parameters
Get Todoist Task1IntegrationTodoistget
Get Notion Task1IntegrationNotiondatabasePage / getAll
Requires completion changeRoutingFilterSee node parameters
Has been completed?RoutingIfSee node parameters
Differences existRoutingFilterSee node parameters
Only continue if not locked1RoutingFilterSee node parameters
Check if Notion ID is lockedData storeRedisget
Get Notion taskIntegrationNotiondatabasePage / get
Check if creating flag existsData storeRedisget
Only continue if flag does not existRoutingFilterSee node parameters
Set creating flagData storeRedisset
Todoist ID exists?RoutingIfSee node parameters
Get todoist IDTransformEdit FieldsSee node parameters
Todoist ID existsRoutingFilterSee node parameters
Get todoist ID1TransformEdit FieldsSee node parameters
Status changed but not emptyRoutingFilterSee node parameters
Status is Obsolete?RoutingIfSee node parameters
Status is not DoneRoutingFilterSee node parameters
Neither focussed nor plannedRoutingIfSee node parameters
Notion trigger referenceRoutingNo OpSee node parameters
Notion WebhookTriggerWebhookSee node parameters
Store Todoist ID1IntegrationNotiondatabasePage / update
Mark as Completed in Todoist1IntegrationTodoistclose
Update task in Todoist1Web / APIHTTP RequestPOST
Globals2TransformEdit FieldsSee node parameters
Mark as Incomplete in Todoist1IntegrationTodoistreopen
Generate UUID2TransformCryptoSee node parameters
Update section (Sync API)2Web / APIHTTP RequestPOST
Lock Todoist ID4Data storeRedisset
Delete Task in Todoist2IntegrationTodoistdelete
Map Notion to Todoist2CodeCodeSee node parameters
Get Todoist Task2IntegrationTodoistget
Create task in Todoist1Web / APIHTTP RequestPOST
Execution Data5IntegrationExecution DataSee node parameters
Execution Data6IntegrationExecution DataSee node parameters
Todoist WebhookTriggerWebhookSee node parameters
Execute WorkflowSubworkflowExecute WorkflowSee node parameters
Extract IDsTransformEdit FieldsSee node parameters
Split out Notion changesTransformSplit OutSee node parameters
Body is array?RoutingIfSee node parameters
Switch by projectRoutingSwitchSee node parameters
WebhookTriggerWebhookSee node parameters
SwitchRoutingSwitchSee node parameters
Execute Workflow1SubworkflowExecute WorkflowSee node parameters
Due date emptyRoutingFilterSee node parameters
Has not been completed?RoutingIfSee node parameters
End hereRoutingNo OpSee node parameters
Retry limit reachedRoutingStop And ErrorSee node parameters
Set triesTransformEdit FieldsSee node parameters
Update triesTransformEdit FieldsSee node parameters
WaitWaitWaitSee node parameters
Catch known errorRoutingIfSee node parameters
If tries leftRoutingIfSee node parameters
Retry limit reached1RoutingStop And ErrorSee node parameters
Set tries1TransformEdit FieldsSee node parameters
Update tries1TransformEdit FieldsSee node parameters
Wait1WaitWaitSee node parameters
Catch known error1RoutingIfSee node parameters
If tries left1RoutingIfSee node parameters
Set tries2TransformEdit FieldsSee node parameters
Update tries2TransformEdit FieldsSee node parameters
Wait2WaitWaitSee node parameters
Catch known error2RoutingIfSee node parameters
If tries left2RoutingIfSee node parameters
Retry limit reached2RoutingStop And ErrorSee node parameters
Turn Markdown into Notion BlocksCodeCodeSee node parameters
Handle each block separatelyTransformSplit OutSee node parameters
Append Notion BlockIntegrationNotionblock

Setup

  1. Download the JSON and import it into an isolated n8n instance. Leave the workflow inactive.
  2. Connect your own credentials through n8n’s credential store. Replace REPLACE_WITH_* values with your resource identifiers.
  3. Inspect API destinations, Code nodes, database queries, file paths, and outbound actions.
  4. Run with synthetic data and harmless destinations. Enable triggers only after checking the actual outputs.

Requirements

An n8n instance with the listed node types and compatible versions. Service-connected workflows may require accounts, credentials, or paid APIs.

Credential types declared in the source

  • gmailOAuth2
  • notionApi
  • redis
  • todoistApi

Workflow notes

Keep in sync

Keep in sync

if you update the mapping, make sure to change it in the other node as well!

Set Globals

Set Globals

Use Sync Setup Helper Workflow to generate the JSON and paste it in every Globals Nodes

Sticky Note14

Update section for each task in Todoist using the Sync API in full sync mode. Use batching and split into multiple batches, if needed, since there is a request limit of 100 full syncs within a 15 minute period.

Sticky Note16

Get open/incomplete tasks from both Notion and Todoist

Sticky Note17

Generate an email report containing all changes made by this workflow, since changes in Todoist are being overwritten.

Sticky Note23

Store more information, which was not available earlier

Sticky Note26

Set focussed flag, so it remains in Todoist even after updating in Notion

Sticky Note27

Create Notion page if it does not exist

Sticky Note28

Update Notion page if it needs to be changed

Sticky Note29

Checking for a cached flag in Redis, to prevent overrides and endless loops.

Sticky Note31

Retrieve Notion page, so it can be compared against

Sticky Note33

Store relevant information in highlighted execution data, in case of debugging

Sticky Note36

Following nodes reference to this node, so the trigger can easily be replaced

Sticky Note37

This prevents the creation of duplicates in Notion, by storing a special flag in Redis containing the Todoist ID

Sticky Note22

Update section for in Todoist using the Sync API in full sync mode (maximum of 100 requests within a 15 minute period!)

Sticky Note24

Delete the task if it does not fit into the filter anymore

Sticky Note25

Update the task if it needs to be changed

Sticky Note38

Notion tries to deliver webhooks again. To ensure the data is up to date, the payload must be retrieved again.

Sticky Note41

Continues on error, so it is not necessary to retrieve task beforehand

Sticky Note45

Instead of deleting pages in Notion, it is a safer practice to mark them as obsolete instead.

Sticky Note46

In rare cases Notion fires multiple webhooks within a short period of time, when creating a new task. This prevents the creation of duplicates in Todoist.

Sticky Note47

The mapping is more advanced and also includes fall-back values for edge-cases

Configure Webhook

Configure Webhook

Grab the webhook URL and use it in the Notion automation. If using the NotionWebhookEmulator instead, replace this node with the nodes shown below.

Sticky Note7

Only sync focussed & planned tasks for efficiency reasons

Sticky Note19

The mapping between the Notion and Todoist tasks is stored in Notion

Sticky Note11

The Todoist ID is being cached in Redis for 15 seconds, so the other Diff sync does not get triggered.

Sticky Note13

Create task if it does not exist (anymore)

Sticky Note6

Edge case: the task has been moved from Obsolete to Done

Todoist Webhook Router Example (optional)

Todoist Webhook Router Example (optional)

Since Todoist sends the updates of all projects to a single webhook, the requests need to be routed accordingly. To do so, create a separate workflow, each containing the flow above, except replace the webhook and switch nodes with an "Execute Workflow Trigger". Then activate the nodes below and configure it, so it calls the sub-workflows accordingly.

Sticky Note51

Using the event type from Todoist is the only option to actually recognize, if a task was deleted

Sticky Note53

Previous node always returns output data, since it uses a filter to retrieve a matching item

Sticky Note54

Reverse mapping the Section/Status

Sticky Note57

The Notion ID is being cached in Redis for 15 seconds, so the other Diff sync does not get triggered.

Sticky Note58

Delete Notion page if Todoist task has been deleted

Sticky Note60

Handle tasks only found in Notion. This can still mean, that the task exists in Todoist, but does not match the filter, since it has been completed. In that case the Todoist task is being retrieved. If it exist, the Todoist Task will be reopened, otherwise it will be created. The Todoist ID is being cached in Redis for 15 seconds, so the Diff sync does not get triggered.

Sticky Note61

Handle tasks which show differences between both systems. If there are differences, the Todoist task will be overwritten by all properties from Notion. Also the status is being updated in the following step accordingly. The Todoist ID is being cached in Redis for 15 seconds, so the Diff sync does not get triggered.

Sticky Note62

Handle tasks only found in Todoist. This can still mean, that the task exists in Notion, but does not match the filter, since it has been completed. In that case the Notion page is being retrieved. If it does not exist, because it was deleted, the Todoist Task will be deleted as well. The Todoist ID is being cached in Redis for 15 seconds, so the Diff sync does not get triggered.

Set Schedule

Set Schedule

Set the preferred period. Usually once a day, likely outside working hours should be a solid option.

Sticky Note64

Preparing data for comparing

Set mail address

Set mail address

Provide the email address where the report should be sent to. This node can also be replaced by an SMTP or similar node.

Sticky Note66

If consuming data from the Notion Webhook Emulator instead of a regular Notion Webhook, the data is structured differently and can contain multiple items. This section makes the workflow compatible with both approaches.

Register Webhook in Todoist App

Register Webhook in Todoist App

Grab the URL from here and add it to the Todosit developer app as described in the setup instructions

Set project ID and name

Set project ID and name

The ID can be copied from JSON in Globals Node. If syncing multiple Todoist projects, use the router described below.

Replace trigger

Replace trigger

Use this trigger in the sub-workflows instead

Register Webhook

Register Webhook

Configure this webhook in the Todoist App as mentioned above

Set project IDs and names

Set project IDs and names

Copy each project ID from the JSON of the different Globals Nodes and name the branches by the projects.

Select sub-workflows

Select sub-workflows

Choose the sub-workflows based on the branch of the Switch Node

0. Start here

0. Start here

Notion

  • A database must already exist (get a basic template here) with the following properties (case matters!):
    • Text: "Name"
    • Status: "Status", containing at least the options "Backlog", "In progress", "Done", "Obsolete"
    • Select: "Priority", containing the options "do first", "urgent", "important"
    • Date: "Due"
    • Checkbox: "Focus"
    • Text: "Todoist ID"

Todoist

  • A project must already exist with the same sections like defined as Status in Notion (except Done and Obsolete)

Redis

Setup credentials in n8n

  • Notion: https://docs.n8n.io/integrations/builtin/credentials/notion/
  • Todoist: https://docs.n8n.io/integrations/builtin/credentials/todoist/
  • Redis: https://docs.n8n.io/integrations/builtin/credentials/redis/

Clone workflow

  • Clone this workflow into your n8n environment and map the credentials (beware, Todoist credentials are also used in HTTP request nodes)
  • Check for every yellow sticky notes. They show instructions which need to be done once
1. Generate config JSON for Globals Nodes

1. Generate config JSON for Globals Nodes

The sync workflows have a Node named "Globals", which contain mapping information for both Notion and Todoist. This flow helps to generate the config, using a multi-form.

How to use

  • Check if the credentials in the Notion and HTTP Request Nodes are set correctly
  • Activate this workflow
  • Open production URL of the form trigger and complete the form
  • Copy the resulting JSON code into all 3 "Globals" nodes
2. Setup Todoist Webhook

2. Setup Todoist Webhook

  • In the Todoist App open Settings > Integrations > Developer > Build Integrations

  • Click "Create a new app"

    • App name: "Notion-Todoist Sync" / Name of your n8n instance
    • App service URL: (leave empty)
  • Click "Create app"

  • Update settings of the app:

    • OAuth redirect URL: Grab the production URL of the "OAuth redirect" Node (from step 3 on the right)
  • Click on "Save settings", leave tab open

  • Activate this workflow

  • Open the Production URL of the "Todoist Webhook Setup Helper" Node (from step 3 on the right)

  • Copy the Client ID and secret from the App settings into the form and submit it

  • When prompted for permissions, click on "Agree"

  • Go back to App settings tab

  • Scroll further down to "Webhook"

  • Update settings of the app:

    • Webhook callback URL: Grab the production URL of the "Todoist Webhook" Node (Todoist to Notion Diff sync)
    • Select these options:
      • item:added
      • item:updated
      • item:completed
      • item:uncompleted
      • item:deleted
  • Click "Save webhook configuration"

  • Click "Activate webhook"

  • Settings can now be closed

4. Setup Notion Webhook

4. Setup Notion Webhook

You can choose from two options and also choose different options for different workspaces, e.g. optionA for a business related workspace and option B for a private workspace. Both options work similarly, option A is a bit easier to setup, yet it is more expensive.

Option A: Notion Webhook via Automation (paid plan required)

  • Create a new automation on the database which should be synced
  • Rename the automation to "Notion-Todoist Sync" or similar
  • Add trigger: "Any property edited"
  • Add action: "Send webhook"
    • URL: Grab the production URL from the "Notion Webhook" Node of this workflow (Notion to Todoist Diff sync)
    • Content: Select all existing properties
  • Click "Save"

Option B: Notion Webhook Emulator (no subscription required)

  • Follow the instructions here: https://n8n.io/workflows/2284-provide-real-time-updates-for-notion-databases-via-webhooks-with-supabase/
  • Use the Webhook URL from the "Notion Webhook" Node of this workflow to push the updates to
3. Activate Todoist Webhook

3. Activate Todoist Webhook

Webhooks are a bit different in Todoist then usual. A Developer App needs to be created and once be connected to via OAuth, to activate the webhook functionality. This flow only works when using the production URL of it's form trigger.

Sticky Note18

Retrieve Todoist task, which can be completed or deleted at this point. Using advanced retry logic, since the "Get many tasks" endpoint does not return completed tasks.

Sticky Note4

Todoist webhooks can arrive late. To ensure the data is up to date, the payload must be retrieved again. Triggers on deletion can overlap, thus try 3 times, then just stop. Using advanced retry logic, since the "Get many tasks" endpoint does not return completed tasks. No more retries needed, if Task does not exist.

Sticky Note30

Overwrite description with Notion URL

Sticky Note34

Copy task description initially to Notion, before it get's replaced in Todoist by the Notion URL

5. Finishing Touches

5. Finishing Touches

Cleanup

Now technically everything is ready, but it is missing better usability and maintainability. So start by moving (cut & paste) the 3 big flows (outlined in blue) to separate n8n workflows. You can name them as follows:

  • Notion-Todoist Full Sync ()
  • Notion-Todoist Diff Sync ()
  • Todoist-Notion Diff Sync ()

Make sure to activate every workflow which requires it. Also save this workflow to avoid duplicates.

First run

  • Execute the Notion-Todoist Full Sync workflow manually first
  • Now that the initial sync is done and you can start using the realtime sync
Realtime Notion Todoist 2-way Sync Template

Realtime Notion Todoist 2-way Sync Template

Purpose and structure

This folder contains the Realtime Notion Todoist 2-way Sync Template workflow. Its executable nodes use aggregate, code, compareDatasets, crypto, executeWorkflow, executeWorkflowTrigger, executionData, filter, form, formTrigger, gmail, html, httpRequest, if, merge, noOp, notion, redis, respondToWebhook, scheduleTrigger, set, splitInBatches, splitOut, stopAndError, switch, todoist, wait, webhook. The table and connection map below are derived from the JSON, not an inferred guarantee of behavior.

Setup

Import inactive. Configure your own credentials and replace REPLACE_WITH_* values. Review external destinations and Code nodes before running.

Entry points

OAuth redirect, Notion-Todoist Sync Setup Helper, Todoist Webhook Setup Helper, Schedule Trigger, Execute Workflow Trigger, Notion Webhook, Todoist Webhook, Webhook

Verification

Sanitized source candidate; not runtime-tested. This overview is generated from node metadata. Read the folder README for the connection map, credential types, and license.

Attribution

https://github.com/Zie619/n8n-workflows/blob/94007c1445d9258a7da116646b79473e7c7c3282/workflows/Webhook/1897_Webhook_Filter_Sync_Webhook.json MIT; preserve source notices.

Canvas section labels

These name areas of the original canvas; they do not include explanatory instructions.

  • Unidirectional sync from Notion to Todoist
  • Trigger based synchronization from Todoist to Notion (part of bi-directional sync)
  • Trigger based synchronization from Notion to Todoist (part of bi-directional sync)

More in Messaging & email