AutomationFlowsData & Sheets › Multi Db Bidirectional Sync Conflict Resolution

Multi Db Bidirectional Sync Conflict Resolution

25-multi-db-bidirectional-sync-conflict-resolution. Uses postgres, mysql, slack. Webhook trigger; 9 nodes.

Webhook trigger★★★★☆ complexity9 nodesPostgresMySQLSlack
Data & Sheets Trigger: Webhook Nodes: 9 Complexity: ★★★★☆ Added:

This workflow follows the Postgres → 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 →

Download .json
{
  "name": "25-multi-db-bidirectional-sync-conflict-resolution",
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "db-sync-trigger",
        "responseMode": "lastNode",
        "options": {}
      },
      "id": "e5f6g7h8-1111-4444-8888-000000000001",
      "name": "Webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        240,
        400
      ]
    },
    {
      "parameters": {
        "jsCode": "// \u0627\u0644\u06af\u0648\u0631\u06cc\u062a\u0645 \u062a\u0634\u062e\u06cc\u0635 \u062a\u063a\u06cc\u06cc\u0631 \u0648 \u0622\u0645\u0627\u062f\u0647\u200c\u0633\u0627\u0632\u06cc \u0645\u062a\u0627\u062f\u06cc\u062a\u0627\nconst item = $input.first().json;\nconst source = item.source_system;\nconst recordId = item.record_id;\nconst data = item.payload_data;\nconst incomingTimestamp = new Date(item.timestamp).getTime();\n\n// \u0627\u06cc\u062c\u0627\u062f Hash \u0633\u0627\u062f\u0647 \u0628\u0631\u0627\u06cc \u062a\u0634\u062e\u06cc\u0635 \u062a\u063a\u06cc\u06cc\u0631 \u0645\u062d\u062a\u0648\u0627\u06cc\u06cc\nconst dataString = JSON.stringify(data);\nlet hash = 0;\nfor (let i = 0; i < dataString.length; i++) {\n  const char = dataString.charCodeAt(i);\n  hash = ((hash << 5) - hash) + char;\n  hash = hash & hash;\n}\n\nreturn [{\n  json: {\n    source_system: source,\n    record_id: recordId,\n    payload_data: data,\n    incoming_timestamp: incomingTimestamp,\n    data_hash: hash.toString(),\n    sync_version: (item.sync_version || 0) + 1,\n    is_loopback: item.is_loopback === true // \u067e\u0631\u0686\u0645 \u0636\u062f \u062d\u0644\u0642\u0647 (Anti-Loop)\n  }\n}];"
      },
      "id": "e5f6g7h8-2222-4444-8888-000000000002",
      "name": "Code: Hash & Metadata",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        460,
        400
      ]
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "SELECT id, data_hash, sync_version, last_updated, source_of_truth FROM central_patients WHERE id = '{{ $json.record_id }}';",
        "options": {}
      },
      "id": "e5f6g7h8-3333-4444-8888-000000000003",
      "name": "PostgreSQL: Get Existing",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.5,
      "position": [
        680,
        400
      ],
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "// \u0627\u0644\u06af\u0648\u0631\u06cc\u062a\u0645 \u067e\u06cc\u0634\u0631\u0641\u062a\u0647 \u062d\u0644 \u062a\u0639\u0627\u0631\u0636 (Conflict Resolution)\nconst incoming = $input.first().json;\nconst dbRecord = $input.first().json.postgresResult?.[0] || null;\n\n// 1. \u0645\u06a9\u0627\u0646\u06cc\u0632\u0645 \u0636\u062f \u062d\u0644\u0642\u0647 (Anti-Loop): \u0627\u06af\u0631 \u062a\u063a\u06cc\u06cc\u0631 \u062a\u0648\u0633\u0637 \u062e\u0648\u062f \u0633\u06cc\u0633\u062a\u0645 sync \u0627\u06cc\u062c\u0627\u062f \u0634\u062f\u0647\u060c \u0646\u0627\u062f\u06cc\u062f\u0647 \u0628\u06af\u06cc\u0631\nif (incoming.is_loopback) {\n  return [{ json: { ...incoming, action: 'skip_loop', reason: 'Anti-loop triggered' } }];\n}\n\n// 2. \u0627\u06af\u0631 \u0631\u06a9\u0648\u0631\u062f \u062c\u062f\u06cc\u062f \u0627\u0633\u062a\nif (!dbRecord) {\n  return [{ json: { ...incoming, action: 'full_sync', reason: 'New record' } }];\n}\n\n// 3. \u0627\u06af\u0631 \u0647\u0634 \u062f\u0627\u062f\u0647\u200c\u0647\u0627 \u06cc\u06a9\u0633\u0627\u0646 \u0627\u0633\u062a\u060c \u062a\u063a\u06cc\u06cc\u0631\u06cc \u0631\u062e \u0646\u062f\u0627\u062f\u0647\nif (dbRecord.data_hash === incoming.data_hash) {\n  return [{ json: { ...incoming, action: 'skip_unchanged', reason: 'Data hash matches' } }];\n}\n\n// 4. \u062a\u0634\u062e\u06cc\u0635 \u062a\u0639\u0627\u0631\u0636 (Conflict Detection)\nconst dbTimestamp = new Date(dbRecord.last_updated).getTime();\nconst timeDiff = Math.abs(incoming.incoming_timestamp - dbTimestamp);\n\n// \u0627\u06af\u0631 \u062a\u0641\u0627\u0648\u062a \u0632\u0645\u0627\u0646\u06cc \u06a9\u0645\u062a\u0631 \u0627\u0632 5 \u062b\u0627\u0646\u06cc\u0647 \u0628\u0627\u0634\u062f\u060c \u062a\u0639\u0627\u0631\u0636 \u0647\u0645\u0632\u0645\u0627\u0646\u06cc (Race Condition) \u062f\u0631 \u0646\u0638\u0631 \u06af\u0631\u0641\u062a\u0647 \u0645\u06cc\u200c\u0634\u0648\u062f\nif (timeDiff < 5000) {\n  // \u0627\u0633\u062a\u0631\u0627\u062a\u0698\u06cc \u062d\u0644 \u062a\u0639\u0627\u0631\u0636: \u0627\u0648\u0644\u0648\u06cc\u062a \u0645\u0646\u0628\u0639 (Source Priority: PostgreSQL > Airtable > MySQL)\n  let winner = incoming.source_system;\n  if (dbRecord.source_of_truth === 'postgresql') winner = 'postgresql';\n  \n  return [{ \n    json: { \n      ...incoming, \n      db_record: dbRecord,\n      action: 'conflict_manual_review', \n      reason: `Race condition detected. Priority given to: ${winner}`,\n      winner: winner\n    } \n  }];\n}\n\n// 5. \u0627\u0633\u062a\u0631\u0627\u062a\u0698\u06cc \u067e\u06cc\u0634\u200c\u0641\u0631\u0636: \u0622\u062e\u0631\u06cc\u0646 \u0646\u0648\u0634\u062a\u0627\u0631 \u0628\u0631\u0646\u062f\u0647 \u0645\u06cc\u200c\u0634\u0648\u062f (Last Write Wins)\nif (incoming.incoming_timestamp > dbTimestamp) {\n  return [{ json: { ...incoming, db_record: dbRecord, action: 'propagate_update', reason: 'Last Write Wins' } }];\n} else {\n  return [{ json: { ...incoming, db_record: dbRecord, action: 'skip_outdated', reason: 'Incoming data is older than DB' } }];\n}"
      },
      "id": "e5f6g7h8-4444-4444-8888-000000000004",
      "name": "Code: Conflict Resolution",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        900,
        400
      ]
    },
    {
      "parameters": {
        "rules": {
          "values": [
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict"
                },
                "conditions": [
                  {
                    "id": "c1",
                    "leftValue": "={{ $json.action }}",
                    "rightValue": "propagate_update",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ]
              }
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict"
                },
                "conditions": [
                  {
                    "id": "c2",
                    "leftValue": "={{ $json.action }}",
                    "rightValue": "conflict_manual_review",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ]
              }
            }
          ]
        },
        "options": {
          "fallbackOutput": "default"
        }
      },
      "id": "e5f6g7h8-5555-4444-8888-000000000005",
      "name": "Switch: Routing",
      "type": "n8n-nodes-base.switch",
      "typeVersion": 3,
      "position": [
        1120,
        400
      ]
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "UPDATE central_patients SET payload_data = '{{ JSON.stringify($json.payload_data) }}', data_hash = '{{ $json.data_hash }}', sync_version = {{ $json.sync_version }}, last_updated = NOW(), source_of_truth = '{{ $json.source_system }}' WHERE id = '{{ $json.record_id }}';",
        "options": {}
      },
      "id": "e5f6g7h8-6666-4444-8888-000000000006",
      "name": "PostgreSQL: Update Central",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.5,
      "position": [
        1340,
        320
      ],
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "UPDATE legacy_patients SET name = '{{ $json.payload_data.name }}', phone = '{{ $json.payload_data.phone }}', last_sync = NOW() WHERE id = '{{ $json.record_id }}';",
        "options": {}
      },
      "id": "e5f6g7h8-7777-4444-8888-000000000007",
      "name": "MySQL: Propagate to Legacy",
      "type": "n8n-nodes-base.mysql",
      "typeVersion": 2.2,
      "position": [
        1560,
        320
      ],
      "credentials": {
        "mySql": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "INSERT INTO sync_audit_log (record_id, source_system, action_taken, reason, timestamp) VALUES ('{{ $json.record_id }}', '{{ $json.source_system }}', '{{ $json.action }}', '{{ $json.reason }}', NOW());",
        "options": {}
      },
      "id": "e5f6g7h8-8888-4444-8888-000000000008",
      "name": "PostgreSQL: Audit Log",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.5,
      "position": [
        1340,
        480
      ],
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "select": "channel",
        "channelId": {
          "__rl": true,
          "value": "C0000000000",
          "mode": "list",
          "cachedResultName": "db-sync-alerts"
        },
        "text": "\ud83d\udea8 *DATA CONFLICT DETECTED* \ud83d\udea8\n\n\ud83d\udcdd *Record ID:* {{ $json.record_id }}\n\ud83d\udd04 *Source:* {{ $json.source_system }}\n\u26a0\ufe0f *Reason:* {{ $json.reason }}\n\ud83c\udfc6 *Resolution:* Priority given to {{ $json.winner }}\n\n\ud83d\udd17 Please review the `sync_audit_log` table for manual intervention if needed.",
        "otherOptions": {}
      },
      "id": "e5f6g7h8-9999-4444-8888-000000000009",
      "name": "Slack: Conflict Alert",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2.2,
      "position": [
        1560,
        480
      ],
      "credentials": {
        "slackApi": {
          "name": "<your credential>"
        }
      }
    }
  ],
  "connections": {
    "Webhook": {
      "main": [
        [
          {
            "node": "Code: Hash & Metadata",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code: Hash & Metadata": {
      "main": [
        [
          {
            "node": "PostgreSQL: Get Existing",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "PostgreSQL: Get Existing": {
      "main": [
        [
          {
            "node": "Code: Conflict Resolution",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code: Conflict Resolution": {
      "main": [
        [
          {
            "node": "Switch: Routing",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Switch: Routing": {
      "main": [
        [
          {
            "node": "PostgreSQL: Update Central",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "PostgreSQL: Audit Log",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "PostgreSQL: Audit Log",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "PostgreSQL: Update Central": {
      "main": [
        [
          {
            "node": "MySQL: Propagate to Legacy",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "PostgreSQL: Audit Log": {
      "main": [
        [
          {
            "node": "Slack: Conflict Alert",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1",
    "saveManualExecutions": true,
    "saveDataErrorExecution": "all",
    "saveDataSuccessExecution": "all"
  },
  "id": "25-multi-db-bidirectional-sync-conflict-resolution",
  "tags": []
}

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.

Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

25-multi-db-bidirectional-sync-conflict-resolution. Uses postgres, mysql, slack. Webhook trigger; 9 nodes.

Source: https://github.com/kooroosh1363/agentic-automation-lab/blob/main/25-multi-db-bidirectional-sync-conflict-resolution/workflow.json — original creator credit. Request a take-down →

More Data & Sheets workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

Data & Sheets

Messy Legacy Workflow. Uses acme-nodes-base, postgres, slack, emailSend. Webhook trigger; 10 nodes.

Acme Nodes Base, Postgres, Slack +3
Data & Sheets

zaad9_teams_dane. Uses emailSend, airtable, slack, microsoftTeams. Webhook trigger; 7 nodes.

Email Send, Airtable, Slack +2
Data & Sheets

CMM. Uses httpRequest, postgres, redis. Webhook trigger; 90 nodes.

HTTP Request, Postgres, Redis
Data & Sheets

This workflow acts as a junior finance research analyst for a UK boutique M&A or corporate finance team. It listens for Slack messages, classifies the request, gathers company or market data, and prod

HTTP Request, Google Drive, Google Docs +5
Data & Sheets

Scraping. Uses httpRequest, postgres, @apify/n8n-nodes-apify, respondToWebhook. Webhook trigger; 61 nodes.

HTTP Request, Postgres, @Apify/N8N Nodes Apify