{
  "name": "Nodbot - Stripe webhook to CRM with idempotency",
  "nodes": [
    {
      "parameters": {
        "path": "integration-stripe-n8n-payment-webhooks",
        "httpMethod": "POST",
        "responseMode": "responseNode"
      },
      "id": "8936ddde-82cd-41b1-84a5-46ad1aecb625",
      "name": "Stripe Webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        0,
        260
      ]
    },
    {
      "parameters": {
        "jsCode": "const event = $json.body ?? $json;\nconst type = String(event.type ?? '').trim();\nconst allowed = ['payment_intent.succeeded', 'checkout.session.completed', 'invoice.paid', 'charge.refunded'];\nif (!allowed.includes(type)) {\n  return [{ json: { action: 'ignore', reason: 'event_not_allowed', type } }];\n}\nconst obj = event.data?.object ?? {};\nconst paymentIntent = String(obj.payment_intent ?? obj.id ?? '').trim();\nconst eventId = String(event.id ?? '').trim();\nif (!eventId || !paymentIntent) throw new Error('No Stripe event_id or payment_intent');\nconst metadata = obj.metadata ?? {};\nconst orderId = String(metadata.order_id ?? '').trim();\nconst dealId = String(metadata.deal_id ?? '').trim();\nif (!orderId && !dealId) throw new Error('Stripe metadata must contain order_id or deal_id');\nreturn [{ json: {\n  action: type.includes('refunded') ? 'mark_refunded' : 'mark_paid',\n  idempotency_key: `stripe:${eventId}`,\n  payment_key: `stripe-payment:${paymentIntent}:${type}`,\n  event_id: eventId,\n  event_type: type,\n  payment_intent: paymentIntent,\n  amount: Number(obj.amount_received ?? obj.amount_paid ?? obj.amount ?? 0) / 100,\n  currency: String(obj.currency ?? '').toUpperCase(),\n  order_id: orderId,\n  deal_id: dealId,\n  livemode: event.livemode === true,\n  crm_comment: `Stripe ${type}: ${paymentIntent}`\n}}];"
      },
      "id": "3e87bb04-ea13-4725-b8fd-0c19297a54f5",
      "name": "Verify and route Stripe event",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        280,
        260
      ]
    },
    {
      "parameters": {
        "jsCode": "const event = $json.body ?? $json;\nconst type = String(event.type ?? '').trim();\nconst allowed = ['payment_intent.succeeded', 'checkout.session.completed', 'invoice.paid', 'charge.refunded'];\nif (!allowed.includes(type)) {\n  return [{ json: { action: 'ignore', reason: 'event_not_allowed', type } }];\n}\nconst obj = event.data?.object ?? {};\nconst paymentIntent = String(obj.payment_intent ?? obj.id ?? '').trim();\nconst eventId = String(event.id ?? '').trim();\nif (!eventId || !paymentIntent) throw new Error('No Stripe event_id or payment_intent');\nconst metadata = obj.metadata ?? {};\nconst orderId = String(metadata.order_id ?? '').trim();\nconst dealId = String(metadata.deal_id ?? '').trim();\nif (!orderId && !dealId) throw new Error('Stripe metadata must contain order_id or deal_id');\nreturn [{ json: {\n  action: type.includes('refunded') ? 'mark_refunded' : 'mark_paid',\n  idempotency_key: `stripe:${eventId}`,\n  payment_key: `stripe-payment:${paymentIntent}:${type}`,\n  event_id: eventId,\n  event_type: type,\n  payment_intent: paymentIntent,\n  amount: Number(obj.amount_received ?? obj.amount_paid ?? obj.amount ?? 0) / 100,\n  currency: String(obj.currency ?? '').toUpperCase(),\n  order_id: orderId,\n  deal_id: dealId,\n  livemode: event.livemode === true,\n  crm_comment: `Stripe ${type}: ${paymentIntent}`\n}}];"
      },
      "id": "12ae77da-c4bf-4628-91bb-e7499283c307",
      "name": "Normalize payment event",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        560,
        260
      ]
    },
    {
      "parameters": {},
      "id": "6205ea93-b8a6-495c-aec5-cc7e8b8a6f09",
      "name": "Check idempotency",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2,
      "position": [
        840,
        260
      ]
    },
    {
      "parameters": {},
      "id": "260a87a9-46d8-4f52-a0b5-f6c53e066ac9",
      "name": "Update CRM or access",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 2,
      "position": [
        1120,
        260
      ]
    },
    {
      "parameters": {},
      "id": "d5255104-b5c3-48ff-b3b4-8f9395b4fc2a",
      "name": "Respond 2xx",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 2,
      "position": [
        1400,
        260
      ]
    }
  ],
  "connections": {
    "Stripe Webhook": {
      "main": [
        [
          {
            "node": "Verify and route Stripe event",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Verify and route Stripe event": {
      "main": [
        [
          {
            "node": "Normalize payment event",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize payment event": {
      "main": [
        [
          {
            "node": "Check idempotency",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check idempotency": {
      "main": [
        [
          {
            "node": "Update CRM or access",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Update CRM or access": {
      "main": [
        [
          {
            "node": "Respond 2xx",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "pinData": {},
  "settings": {
    "executionOrder": "v1"
  },
  "staticData": null,
  "tags": [
    "nodbot",
    "integration",
    "production"
  ],
  "triggerCount": 1,
  "updatedAt": "2026-05-30T00:00:00.000Z",
  "versionId": "53aeba60-8c5a-4c37-8027-9172251653e6",
  "meta": {
    "templateCredsSetupCompleted": false
  }
}