AutomationFlowsWeb Scraping › Prevent Duplicate Webhook Executions with Aari Idempotency

Prevent Duplicate Webhook Executions with Aari Idempotency

Original n8n title: Prevent Duplicate Webhook Executions with Aari Idempotency Gate

ByNesho Neshev @neshkito on n8n.io

This template is for anyone running n8n workflows that receive webhooks and perform side effects such as payments, emails, database inserts, or API calls.

Webhook trigger★★★☆☆ complexity9 nodesHTTP Request
Web Scraping Trigger: Webhook Nodes: 9 Complexity: ★★★☆☆ Added:

This workflow corresponds to n8n.io template #13863 — we link there as the canonical source.

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
{
  "meta": {
    "templateCredsSetupCompleted": false
  },
  "name": "Prevent duplicate webhook executions in n8n",
  "tags": [],
  "nodes": [
    {
      "id": "sticky-explain",
      "name": "What this does",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -400,
        80
      ],
      "parameters": {
        "color": 7,
        "width": 380,
        "height": 500,
        "content": "## Prevent duplicate webhook executions\n\nMany webhook providers use at-least-once delivery.\nIf a webhook times out or fails, the same event may be sent again.\nFrom n8n's perspective it looks like a new trigger \u2014 the workflow executes twice.\n\nThis template adds an idempotency check before any side effect:\n\u2022 Stripe charge\n\u2022 Email send\n\u2022 Database insert\n\u2022 API call\n\nIf the same `idempotency_key` appears again within 24 hours,\nAARI returns `decision: BLOCK` and the workflow stops.\n\n\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\nFingerprint fallback order\n\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\n1. body.id / event_id / eventId / webhook_id\n2. headers x-event-id / x-request-id\n3. type:event:action:created:data.object.id:order_id:\u2026\n4. execution.id (same-execution only \u2014 not retry-safe)\n\n\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\nWho this is for\n\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\nAnyone running n8n workflows that receive webhooks and perform side effects \u2014 payments, emails, DB writes, API calls.\n\n\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\nRequirements\n\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\nFree AARI account \u2192 api.getaari.com/n8n\n2,500 gate calls/month \u00b7 No credit card"
      },
      "typeVersion": 1
    },
    {
      "id": "sticky-setup",
      "name": "Setup",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -400,
        620
      ],
      "parameters": {
        "color": 4,
        "width": 380,
        "height": 340,
        "content": "## Setup (2 minutes)\n\n**1. Get a free AARI API key:**\nhttps://api.getaari.com/n8n\n\n**2. Create n8n credential:**\nCredentials \u2192 New \u2192 Header Auth\nHeader name: `X-API-Key`\nValue: your AARI key\n\nSelect it in the **AARI idempotency gate** node.\n\n**3. idempotency_key is pre-set** \u2014 auto-detects the event ID from the payload (Stripe, GitHub, Shopify, etc.) or falls back to a body fingerprint.\n\n**4. Connect your action:**\nReplace **\u2705 Run your action here** with your real node \u2014 Stripe charge, send email, DB insert, API call, etc."
      },
      "typeVersion": 1
    },
    {
      "id": "sticky-test",
      "name": "Test",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -400,
        1000
      ],
      "parameters": {
        "color": 3,
        "width": 380,
        "height": 160,
        "content": "## Test it\n\nSend the same webhook payload twice.\n\nFirst call \u2192 `decision: ALLOW`\nSecond call \u2192 `decision: BLOCK`\n\nThe duplicate is caught before any action runs."
      },
      "typeVersion": 1
    },
    {
      "id": "webhook-node",
      "name": "Incoming webhook event",
      "type": "n8n-nodes-base.webhook",
      "position": [
        240,
        300
      ],
      "parameters": {
        "path": "webhook-dedup",
        "options": {},
        "httpMethod": "POST",
        "responseMode": "onReceived"
      },
      "typeVersion": 2
    },
    {
      "id": "aari-gate-node",
      "name": "AARI idempotency gate",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        480,
        300
      ],
      "parameters": {
        "url": "https://api.getaari.com/gate",
        "method": "POST",
        "options": {
          "timeout": 10000
        },
        "sendBody": true,
        "specifyBody": "keypair",
        "authentication": "genericCredentialType",
        "bodyParameters": {
          "parameters": [
            {
              "name": "agent_id",
              "value": "={{ $workflow.name }}"
            },
            {
              "name": "run_id",
              "value": "={{ $execution.id }}"
            },
            {
              "name": "action_type",
              "value": "workflow.side_effect"
            },
            {
              "name": "resource",
              "value": "n8n"
            },
            {
              "name": "idempotency_key",
              "value": "={{ $json.body?.id || $json.body?.event_id || $json.body?.eventId || $json.body?.webhook_id || $json.headers?.['x-event-id'] || $json.headers?.['x-request-id'] || [$json.body?.type, $json.body?.event, $json.body?.action, $json.body?.created, $json.body?.data?.object?.id, $json.body?.resource_id, $json.body?.order_id, $json.body?.customer_id, $json.body?.object_id].filter(v => v != null && v !== '').join(':') || $execution.id + '_action' }}"
            },
            {
              "name": "environment",
              "value": "prod"
            },
            {
              "name": "mode",
              "value": "advisory"
            }
          ]
        },
        "genericAuthType": "httpHeaderAuth"
      },
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.1
    },
    {
      "id": "if-block-node",
      "name": "Duplicate detected?",
      "type": "n8n-nodes-base.if",
      "position": [
        720,
        300
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "decision-check",
              "operator": {
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $json.decision }}",
              "rightValue": "BLOCK"
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "blocked-node",
      "name": "\u26d4 Stop duplicate execution",
      "type": "n8n-nodes-base.set",
      "position": [
        960,
        160
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "blocked-status",
              "name": "status",
              "type": "string",
              "value": "duplicate_blocked"
            },
            {
              "id": "blocked-reason",
              "name": "reason",
              "type": "string",
              "value": "={{ $json.policy_hits ? $json.policy_hits.join(', ') : 'idempotency.duplicate' }}"
            }
          ]
        }
      },
      "typeVersion": 3.3
    },
    {
      "id": "allowed-node",
      "name": "\u2705 Run your action here",
      "type": "n8n-nodes-base.set",
      "position": [
        960,
        440
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "allowed-status",
              "name": "status",
              "type": "string",
              "value": "approved"
            },
            {
              "id": "allowed-decision",
              "name": "decision",
              "type": "string",
              "value": "={{ $json.decision }}"
            },
            {
              "id": "allowed-action-id",
              "name": "action_id",
              "type": "string",
              "value": "={{ $json.action_id }}"
            }
          ]
        }
      },
      "typeVersion": 3.3
    },
    {
      "id": "complete-node",
      "name": "\ud83d\udccb Report SUCCESS",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1200,
        440
      ],
      "parameters": {
        "url": "={{ 'https://api.getaari.com/actions/' + $json.action_id + '/complete' }}",
        "method": "POST",
        "options": {
          "timeout": 5000
        },
        "jsonBody": "{\n  \"outcome\": \"SUCCESS\"\n}",
        "sendBody": true,
        "specifyBody": "json",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth"
      },
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.1
    }
  ],
  "notes": "Prevent duplicate webhook executions using AARI idempotency.\n\nQuick start:\n1. Get a free key: https://api.getaari.com/n8n\n2. Create Header Auth credential: X-API-Key \u2192 your key\n3. Replace 'Run your action here' with your real action node",
  "settings": {
    "executionOrder": "v1"
  },
  "staticData": null,
  "connections": {
    "Duplicate detected?": {
      "main": [
        [
          {
            "node": "\u26d4 Stop duplicate execution",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "\u2705 Run your action here",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AARI idempotency gate": {
      "main": [
        [
          {
            "node": "Duplicate detected?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Incoming webhook event": {
      "main": [
        [
          {
            "node": "AARI idempotency gate",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\u2705 Run your action here": {
      "main": [
        [
          {
            "node": "\ud83d\udccb Report SUCCESS",
            "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.

Pro

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

About this workflow

This template is for anyone running n8n workflows that receive webhooks and perform side effects such as payments, emails, database inserts, or API calls.

Source: https://n8n.io/workflows/13863/ — original creator credit. Request a take-down →

More Web Scraping workflows → · Browse all categories →

Related workflows

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

Web Scraping

Community Node Disclaimer: This workflow uses KlickTipp community nodes.

Crypto, Custom, N8N Nodes Klicktipp +1
Web Scraping

Automatically sync Fizzy cards to Basecamp todos in real-time. When cards are created, assigned, completed, or reopened in Fizzy, the changes sync instantly to your Basecamp project. Card tags determi

HTTP Request, N8N Nodes Basecamp
Web Scraping

This workflow automates accounts payable: upload a PDF invoice, let Claude AI extract the key fields, and automatically create a vendor bill (incoming invoice) in Odoo 18.

HTTP Request
Web Scraping

This n8n workflow automates airline customer support by classifying travel-related questions, fetching relevant information, generating AI answers, and delivering structured responses to users. It ens

HTTP Request
Web Scraping

This n8n template lets you automatically pull market data for the cryptocurrencies from CoinGecko every hour, calculate custom volatility and market-health metrics, classify each coin’s price action i

HTTP Request