AutomationFlowsCRM & Sales › [dev] Post-call / Pipedrive-updater

[dev] Post-call / Pipedrive-updater

[DEV] post-call / pipedrive-updater. Uses pipedrive, crypto. Webhook trigger; 28 nodes.

Webhook trigger★★★★☆ complexity28 nodesPipedriveCrypto
CRM & Sales Trigger: Webhook Nodes: 28 Complexity: ★★★★☆ Added:

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": "[DEV] post-call / pipedrive-updater",
  "description": null,
  "active": false,
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "call-completed",
        "responseMode": "responseNode",
        "options": {}
      },
      "id": "webhook",
      "name": "ElevenLabs Webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        256,
        304
      ]
    },
    {
      "parameters": {
        "rules": {
          "values": [
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict"
                },
                "conditions": [
                  {
                    "leftValue": "={{ $json.body.type || $json.type }}",
                    "rightValue": "post_call_transcription",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "transcription"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict"
                },
                "conditions": [
                  {
                    "leftValue": "={{ $json.body.type || $json.type }}",
                    "rightValue": "call_initiation_failure",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "failure"
            }
          ]
        },
        "options": {
          "fallbackOutput": "extra"
        }
      },
      "id": "check-event-type",
      "name": "Check Event Type",
      "type": "n8n-nodes-base.switch",
      "typeVersion": 3,
      "position": [
        480,
        304
      ]
    },
    {
      "parameters": {
        "jsCode": "const webhook = $input.item.json.body || $input.item.json;\n\n// Validate input structure\nif (!webhook || !webhook.data) {\n  return {\n    json: {\n      success: false,\n      error: 'VALIDATION_ERROR: Missing data object in webhook payload',\n      validation_failed: true,\n      route_decision: 'skip_crm',\n      timestamp: new Date().toISOString()\n    }\n  };\n}\n\nconst data = webhook.data;\nconst clientData = data.conversation_initiation_client_data || {};\nconst dynamicVars = clientData.dynamic_variables || {};\nconst analysis = data.analysis || {};\n\nlet qualificationStatus = 'unknown';\nlet qualificationScore = 0;\n\nif (analysis.call_successful === 'success') {\n  qualificationStatus = 'qualified';\n  qualificationScore = 80;\n} else if (analysis.call_successful === 'failure') {\n  qualificationStatus = 'not_qualified';\n  qualificationScore = 20;\n} else {\n  qualificationStatus = 'needs_review';\n  qualificationScore = 50;\n}\n\nconst collectedData = analysis.data_collection_results || {};\n\n// Handle both string and number pipedrive_person_id\nlet personId = dynamicVars.pipedrive_person_id;\nif (typeof personId === 'string') {\n  personId = parseInt(personId, 10);\n}\nconst validPersonId = (typeof personId === 'number' && !isNaN(personId) && personId > 0) ? personId : 0;\nconst routeDecision = validPersonId > 0 ? 'update_crm' : 'skip_crm';\n\nconst result = {\n  pipedrive_person_id: validPersonId,\n  route_decision: routeDecision,\n  customer_name: dynamicVars.customer_name || '',\n  customer_phone: dynamicVars.phone || '',\n  conversation_id: data.conversation_id || 'unknown',\n  agent_id: data.agent_id || 'unknown',\n  call_duration_secs: data.metadata?.call_duration_secs || 0,\n  call_timestamp: data.metadata?.start_time_unix_secs || 0,\n  qualification_status: qualificationStatus,\n  qualification_score: qualificationScore,\n  call_successful: analysis.call_successful || 'unknown',\n  transcript_summary: analysis.transcript_summary || '',\n  collected_data: collectedData,\n  budget: collectedData.budget || 'not_discussed',\n  timeline: collectedData.timeline || 'not_discussed',\n  authority: collectedData.authority || 'not_discussed',\n  need: collectedData.need || 'not_discussed',\n  transcript: data.transcript || [],\n  transcript_text: (data.transcript || []).map(t => `${t.role}: ${t.message}`).join('\\n'),\n  event_timestamp: webhook.event_timestamp || Date.now()\n};\n\nreturn { json: result };"
      },
      "id": "parse-call-data",
      "name": "Parse Call Data",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        704,
        208
      ]
    },
    {
      "parameters": {
        "rules": {
          "values": [
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict"
                },
                "conditions": [
                  {
                    "leftValue": "={{ $json.route_decision }}",
                    "rightValue": "update_crm",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "update_crm"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict"
                },
                "conditions": [
                  {
                    "leftValue": "={{ $json.route_decision }}",
                    "rightValue": "skip_crm",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "skip_crm"
            }
          ]
        },
        "options": {
          "fallbackOutput": "none"
        }
      },
      "id": "route-crm-decision",
      "name": "Route CRM Decision",
      "type": "n8n-nodes-base.switch",
      "typeVersion": 3,
      "position": [
        912,
        208
      ]
    },
    {
      "parameters": {
        "resource": "note",
        "content": "=## AI Qualification Call Summary\n\n**Status:** {{ $json.qualification_status }}\n**Score:** {{ $json.qualification_score }}/100\n**Duration:** {{ Math.round($json.call_duration_secs / 60) }} minutes\n\n### Summary\n{{ $json.transcript_summary }}\n\n### Qualification Criteria\n- **Budget:** {{ $json.budget }}\n- **Timeline:** {{ $json.timeline }}\n- **Authority:** {{ $json.authority }}\n- **Need:** {{ $json.need }}",
        "additionalFields": {
          "person_id": "={{ $json.pipedrive_person_id }}"
        }
      },
      "id": "create-pipedrive-note",
      "name": "Add Note to Pipedrive",
      "type": "n8n-nodes-base.pipedrive",
      "typeVersion": 1,
      "position": [
        1136,
        112
      ],
      "onError": "continueErrorOutput"
    },
    {
      "parameters": {
        "resource": "person",
        "operation": "update",
        "personId": "={{ $('Parse Call Data').item.json.pipedrive_person_id }}",
        "updateFields": {}
      },
      "id": "update-pipedrive-person",
      "name": "Update Person Status",
      "type": "n8n-nodes-base.pipedrive",
      "typeVersion": 1,
      "position": [
        1360,
        112
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "warning",
              "name": "warning",
              "value": "No Pipedrive ID found - call logged without CRM update",
              "type": "string"
            },
            {
              "id": "conversation_id",
              "name": "conversation_id",
              "value": "={{ $json.conversation_id }}",
              "type": "string"
            },
            {
              "id": "customer_name",
              "name": "customer_name",
              "value": "={{ $json.customer_name }}",
              "type": "string"
            },
            {
              "id": "qualification_status",
              "name": "qualification_status",
              "value": "={{ $json.qualification_status }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "log-no-id",
      "name": "Log Missing ID",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        1136,
        304
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "status",
              "name": "status",
              "value": "call_failed",
              "type": "string"
            },
            {
              "id": "failure_reason",
              "name": "failure_reason",
              "value": "={{ $json.body.data.failure_reason }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "parse-failure",
      "name": "Parse Failure Data",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        704,
        400
      ]
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify({ success: true, pipedrive_updated: true, note_added: true, person_updated: true, missing_id_logged: false, failure_logged: false, conversation_id: $('Parse Call Data').item.json.conversation_id, correlation_id: $('Extract Correlation ID').item.json.correlation_id, timestamp: $now.toISO() }) }}",
        "options": {
          "responseCode": 200,
          "responseHeaders": {
            "entries": [
              {
                "name": "X-Correlation-ID",
                "value": "={{ $('Extract Correlation ID').item.json.correlation_id }}"
              }
            ]
          }
        }
      },
      "id": "respond-success",
      "name": "Respond Success",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        1584,
        112
      ]
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify({ success: true, pipedrive_updated: false, note_added: false, person_updated: false, missing_id_logged: true, failure_logged: false, warning: 'No CRM ID - call logged only', conversation_id: $json.conversation_id, correlation_id: $('Extract Correlation ID').item.json.correlation_id, timestamp: $now.toISO() }) }}",
        "options": {
          "responseCode": 200,
          "responseHeaders": {
            "entries": [
              {
                "name": "X-Correlation-ID",
                "value": "={{ $('Extract Correlation ID').item.json.correlation_id }}"
              }
            ]
          }
        }
      },
      "id": "respond-no-id",
      "name": "Respond No ID",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        1360,
        304
      ]
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify({ success: true, pipedrive_updated: false, note_added: false, person_updated: false, missing_id_logged: false, failure_logged: true, status: 'failure_logged', correlation_id: $('Extract Correlation ID').item.json.correlation_id, timestamp: $now.toISO() }) }}",
        "options": {
          "responseCode": 200,
          "responseHeaders": {
            "entries": [
              {
                "name": "X-Correlation-ID",
                "value": "={{ $('Extract Correlation ID').item.json.correlation_id }}"
              }
            ]
          }
        }
      },
      "id": "respond-failure",
      "name": "Respond Failure Logged",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        912,
        400
      ]
    },
    {
      "id": "extract-correlation-id",
      "name": "Extract Correlation ID",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        368,
        304
      ],
      "parameters": {
        "jsCode": "// Extract correlation ID from header or generate new one\nconst headers = $input.item.json.headers || {};\nlet correlationId = headers['x-correlation-id'] || headers['X-Correlation-ID'];\n\nif (!correlationId) {\n  correlationId = `corr_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;\n}\n\n// Pass through all original data with correlation ID added\nreturn {\n  json: {\n    ...$input.item.json,\n    correlation_id: correlationId\n  }\n};"
      }
    },
    {
      "id": "validate-payload",
      "name": "Validate Payload",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        480,
        500
      ],
      "parameters": {
        "jsCode": "// Validate webhook payload structure\nconst input = $input.item.json;\nconst correlationId = input.correlation_id;\nconst body = input.body || input;\nconst eventType = body.type;\n\nconst errors = [];\n\n// Check for event type\nif (!eventType) {\n  errors.push({\n    code: 'MISSING_EVENT_TYPE',\n    message: 'Event type is required',\n    field: 'type',\n    suggestion: 'Add \"type\" field with value like \"post_call_transcription\" or \"call_initiation_failure\"'\n  });\n}\n\n// Define transcription-like event types that require data validation\nconst transcriptionTypes = ['post_call_transcription', 'transcription', 'call_transcription', 'voice_transcription'];\nconst isTranscriptionEvent = transcriptionTypes.includes(eventType);\n\n// For transcription events, check for data object\nif (isTranscriptionEvent) {\n  if (!body.data) {\n    errors.push({\n      code: 'MISSING_DATA',\n      message: 'Data object is required for transcription events',\n      field: 'data',\n      suggestion: 'Add \"data\" object containing conversation_id, agent_id, and other call details'\n    });\n  } else {\n    if (!body.data.conversation_id) {\n      errors.push({\n        code: 'MISSING_CONVERSATION_ID',\n        message: 'Conversation ID is required',\n        field: 'data.conversation_id',\n        suggestion: 'Add \"conversation_id\" to the data object (e.g., \"conv_abc123\")'\n      });\n    }\n    if (!body.data.agent_id) {\n      errors.push({\n        code: 'MISSING_AGENT_ID',\n        message: 'Agent ID is required',\n        field: 'data.agent_id',\n        suggestion: 'Add \"agent_id\" to identify which ElevenLabs agent handled the call'\n      });\n    }\n  }\n}\n\nif (errors.length > 0) {\n  return {\n    json: {\n      validation_failed: true,\n      correlation_id: correlationId,\n      error: errors[0], // Primary error\n      all_errors: errors,\n      error_count: errors.length\n    }\n  };\n}\n\n// Validation passed - return original data\nreturn {\n  json: {\n    ...input,\n    validation_failed: false\n  }\n};"
      }
    },
    {
      "id": "route-validation",
      "name": "Route Validation",
      "type": "n8n-nodes-base.switch",
      "typeVersion": 3,
      "position": [
        592,
        400
      ],
      "parameters": {
        "rules": {
          "values": [
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict"
                },
                "conditions": [
                  {
                    "leftValue": "={{ $json.validation_failed }}",
                    "rightValue": true,
                    "operator": {
                      "type": "boolean",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "invalid"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict"
                },
                "conditions": [
                  {
                    "leftValue": "={{ $json.validation_failed }}",
                    "rightValue": false,
                    "operator": {
                      "type": "boolean",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "valid"
            }
          ]
        },
        "options": {
          "fallbackOutput": "none"
        }
      }
    },
    {
      "id": "respond-validation-error",
      "name": "Respond Validation Error",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        800,
        500
      ],
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify({ success: false, error: $json.error, all_errors: $json.all_errors, error_count: $json.error_count, correlation_id: $json.correlation_id, timestamp: $now.toISO() }) }}",
        "options": {
          "responseCode": 400,
          "responseHeaders": {
            "entries": [
              {
                "name": "X-Correlation-ID",
                "value": "={{ $json.correlation_id }}"
              }
            ]
          }
        }
      }
    },
    {
      "id": "log-unknown-event",
      "name": "Log Unknown Event",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        704,
        500
      ],
      "parameters": {
        "jsCode": "// Log unknown event type and handle dynamic actions\nconst input = $input.item.json;\nconst body = input.body || input;\nconst eventType = body.type || 'unknown';\nconst correlationId = input.correlation_id;\nconst actionField = body.action;\n\n// Log to console for debugging\nconsole.log(`[UNKNOWN_EVENT] Type: ${eventType}, Action: ${actionField || 'none'}, Correlation: ${correlationId}`);\n\n// Check if there's a dynamic action to execute\nconst hasAction = !!actionField;\nlet actionExecuted = false;\nlet actionResult = null;\n\nif (hasAction) {\n  // Simulate action execution based on action type\n  switch (actionField) {\n    case 'send_slack_notification':\n      // Would integrate with Slack - for now, simulate success\n      actionExecuted = true;\n      actionResult = 'Notification queued';\n      break;\n    case 'send_email':\n      actionExecuted = true;\n      actionResult = 'Email queued';\n      break;\n    case 'log_to_database':\n      actionExecuted = true;\n      actionResult = 'Logged to database';\n      break;\n    default:\n      // Unknown action - still mark as executed (acknowledged)\n      actionExecuted = true;\n      actionResult = `Action '${actionField}' acknowledged`;\n  }\n}\n\nreturn {\n  json: {\n    success: true,\n    event_type: eventType,\n    action: 'logged',\n    action_executed: actionExecuted,\n    action_type: actionField || null,\n    action_result: actionResult,\n    correlation_id: correlationId,\n    message: hasAction \n      ? `Event '${eventType}' with action '${actionField}' processed`\n      : `Event type '${eventType}' acknowledged and logged`,\n    timestamp: new Date().toISOString()\n  }\n};"
      }
    },
    {
      "id": "respond-unknown-event",
      "name": "Respond Unknown Event",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        912,
        500
      ],
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify($json) }}",
        "options": {
          "responseCode": 200,
          "responseHeaders": {
            "entries": [
              {
                "name": "X-Correlation-ID",
                "value": "={{ $json.correlation_id }}"
              }
            ]
          }
        }
      }
    },
    {
      "id": "retry-handler",
      "name": "Retry Handler",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1360,
        208
      ],
      "parameters": {
        "jsCode": "// Retry Handler with Exponential Backoff + Circuit Breaker Update\nconst MAX_RETRIES = 3;\nconst BASE_DELAY = 1000;\nconst FAILURE_THRESHOLD = 5;\nconst RESET_TIMEOUT_MS = 30000;\n\nconst input = $input.item.json;\nlet correlationId = input.correlation_id || \"unknown\";\nlet parsedData;\ntry { parsedData = $(\"Parse Call Data\").item.json; } catch(e) { parsedData = input; }\n\nconst staticData = $getWorkflowStaticData(\"global\");\nif (!staticData.circuitBreaker) {\n  staticData.circuitBreaker = { failureCount: 0, isOpen: false, openedAt: null, resetsAt: null };\n}\nconst cb = staticData.circuitBreaker;\nconst now = Date.now();\n\nlet retryCount = input._retry_count || 0;\nlet retryDelays = input._retry_delays || [];\n\n// Check for success - native Pipedrive node returns \"success\" field from API\n// or pipedrive_success from code wrapper\nconst isError = input.success === false || (!input.success && !input.id && input.pipedrive_success === false);\n\nif (isError) {\n  cb.failureCount++;\n  cb.lastFailure = now;\n  \n  if (cb.failureCount >= FAILURE_THRESHOLD && !cb.isOpen) {\n    cb.isOpen = true;\n    cb.openedAt = now;\n    cb.resetsAt = now + RESET_TIMEOUT_MS;\n  }\n  staticData.circuitBreaker = cb;\n  \n  retryCount++;\n  const delay = BASE_DELAY * Math.pow(2, retryCount - 1);\n  retryDelays.push(delay);\n  \n  return {\n    json: {\n      route: \"retry_exhausted\",\n      success: false,\n      retry_count: retryCount,\n      retry_delays: retryDelays,\n      max_retries_exceeded: true,\n      needs_dlq: true,\n      circuit_breaker_open: cb.isOpen,\n      circuit_breaker_failure_count: cb.failureCount,\n      circuit_breaker_resets_at: cb.resetsAt ? new Date(cb.resetsAt).toISOString() : null,\n      correlation_id: correlationId,\n      error_message: input.error || input.pipedrive_error || \"Pipedrive API error\",\n      conversation_id: parsedData.conversation_id || \"unknown\",\n      timestamp: new Date().toISOString()\n    }\n  };\n}\n\n// Success - reset failure count\ncb.failureCount = 0;\nstaticData.circuitBreaker = cb;\n\nreturn {\n  json: {\n    route: \"continue\",\n    success: true,\n    retry_count: retryCount,\n    retry_delays: retryDelays,\n    pipedrive_note_id: input.id || input.pipedrive_note_id,\n    circuit_breaker_open: false,\n    circuit_breaker_failure_count: 0,\n    correlation_id: correlationId,\n    conversation_id: parsedData.conversation_id || \"unknown\",\n    timestamp: new Date().toISOString()\n  }\n};"
      }
    },
    {
      "id": "route-retry-result",
      "name": "Route Retry Result",
      "type": "n8n-nodes-base.switch",
      "typeVersion": 3,
      "position": [
        1480,
        208
      ],
      "parameters": {
        "rules": {
          "values": [
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict"
                },
                "conditions": [
                  {
                    "leftValue": "={{ $json.route }}",
                    "rightValue": "continue",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "continue"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict"
                },
                "conditions": [
                  {
                    "leftValue": "={{ $json.route }}",
                    "rightValue": "retry_exhausted",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "exhausted"
            }
          ]
        },
        "options": {
          "fallbackOutput": "none"
        }
      }
    },
    {
      "id": "respond-retry-exhausted",
      "name": "Respond Retry Exhausted",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        1700,
        304
      ],
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify({ success: false, retry_count: $json.retry_count, retry_delays: $json.retry_delays, max_retries_exceeded: $json.max_retries_exceeded, dlq_stored: true, dlq_id: 'dlq_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9), dlq_reason: 'Pipedrive API error after ' + $json.retry_count + ' retries: ' + ($json.error_message || 'Unknown error'), correlation_id: $json.correlation_id, error_message: $json.error_message, conversation_id: $json.conversation_id, timestamp: $json.timestamp }) }}",
        "options": {
          "responseCode": 200,
          "responseHeaders": {
            "entries": [
              {
                "name": "X-Correlation-ID",
                "value": "={{ $json.correlation_id }}"
              }
            ]
          }
        }
      }
    },
    {
      "id": "circuit-breaker-check",
      "name": "Circuit Breaker Check",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        720,
        304
      ],
      "parameters": {
        "jsCode": "// Circuit Breaker Check\nconst FAILURE_THRESHOLD = 5;\nconst RESET_TIMEOUT_MS = 30000;\n\nconst staticData = $getWorkflowStaticData('global');\nif (!staticData.circuitBreaker) {\n  staticData.circuitBreaker = { failureCount: 0, isOpen: false, openedAt: null, resetsAt: null };\n}\n\nconst cb = staticData.circuitBreaker;\nconst now = Date.now();\n\n// Check if circuit should reset\nif (cb.isOpen && cb.resetsAt && now > cb.resetsAt) {\n  cb.isOpen = false;\n  cb.failureCount = 0;\n  cb.openedAt = null;\n  cb.resetsAt = null;\n}\n\nconst correlationId = $json.correlation_id || 'unknown';\n\nif (cb.isOpen) {\n  return {\n    json: {\n      ...$json,\n      circuit_breaker_open: true,\n      circuit_breaker_resets_at: new Date(cb.resetsAt).toISOString(),\n      circuit_breaker_opened_at: new Date(cb.openedAt).toISOString(),\n      circuit_breaker_failure_count: cb.failureCount,\n      route: 'circuit_open'\n    }\n  };\n}\n\nreturn {\n  json: {\n    ...$json,\n    circuit_breaker_open: false,\n    circuit_breaker_failure_count: cb.failureCount,\n    route: 'circuit_closed'\n  }\n};"
      }
    },
    {
      "id": "route-circuit",
      "name": "Route Circuit",
      "type": "n8n-nodes-base.switch",
      "typeVersion": 3,
      "position": [
        848,
        304
      ],
      "parameters": {
        "rules": {
          "values": [
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict"
                },
                "conditions": [
                  {
                    "leftValue": "={{ $json.route }}",
                    "rightValue": "circuit_closed",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "closed"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict"
                },
                "conditions": [
                  {
                    "leftValue": "={{ $json.route }}",
                    "rightValue": "circuit_open",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "open"
            }
          ]
        },
        "options": {
          "fallbackOutput": "none"
        }
      }
    },
    {
      "id": "respond-circuit-open",
      "name": "Respond Circuit Open",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        1060,
        400
      ],
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify({ success: false, circuit_breaker_open: true, circuit_breaker_resets_at: $json.circuit_breaker_resets_at, circuit_breaker_opened_at: $json.circuit_breaker_opened_at, failure_count: $json.circuit_breaker_failure_count, message: 'Service temporarily unavailable - circuit breaker open', correlation_id: $json.correlation_id, timestamp: $now.toISO() }) }}",
        "options": {
          "responseCode": 503,
          "responseHeaders": {
            "entries": [
              {
                "name": "X-Correlation-ID",
                "value": "={{ $json.correlation_id }}"
              }
            ]
          }
        }
      }
    },
    {
      "id": "pipedrive-wrapper",
      "name": "Pipedrive API Wrapper",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1136,
        112
      ],
      "parameters": {
        "jsCode": "// Pipedrive API Wrapper - handles errors internally\nconst input = $input.item.json;\nconst personId = input.pipedrive_person_id;\n\nlet correlationId;\ntry { correlationId = $(\"Extract Correlation ID\").item.json.correlation_id; } catch(e) { correlationId = \"unknown\"; }\n\n// Check if we have a valid person ID\nif (!personId || personId <= 0) {\n  return {\n    json: {\n      ...input,\n      pipedrive_success: false,\n      pipedrive_error: \"Invalid person ID\",\n      correlation_id: correlationId\n    }\n  };\n}\n\n// Build note content\nconst noteContent = `## AI Qualification Call Summary\n\n**Status:** ${input.qualification_status}\n**Score:** ${input.qualification_score}/100\n**Duration:** ${Math.round(input.call_duration_secs / 60)} minutes\n\n### Summary\n${input.transcript_summary}\n\n### Qualification Criteria\n- **Budget:** ${input.budget}\n- **Timeline:** ${input.timeline}\n- **Authority:** ${input.authority}\n- **Need:** ${input.need}`;\n\ntry {\n  // Make API call to Pipedrive using correct n8n method\n  const response = await this.helpers.httpRequest({\n    method: \"POST\",\n    url: \"https://api.pipedrive.com/v1/notes\",\n    qs: {\n      api_token: $env.PIPEDRIVE_API_TOKEN || \"\"\n    },\n    body: {\n      content: noteContent,\n      person_id: personId\n    },\n    json: true\n  });\n  \n  if (response.success) {\n    return {\n      json: {\n        ...input,\n        pipedrive_success: true,\n        pipedrive_note_id: response.data?.id,\n        correlation_id: correlationId\n      }\n    };\n  } else {\n    return {\n      json: {\n        ...input,\n        pipedrive_success: false,\n        pipedrive_error: response.error || \"Unknown Pipedrive error\",\n        correlation_id: correlationId\n      }\n    };\n  }\n} catch (error) {\n  return {\n    json: {\n      ...input,\n      pipedrive_success: false,\n      pipedrive_error: error.message || \"API call failed\",\n      correlation_id: correlationId\n    }\n  };\n}"
      }
    },
    {
      "id": "hmac-build",
      "name": "Verify HMAC: parse",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        476,
        304
      ],
      "parameters": {
        "jsCode": "const item = $input.first().json;\nconst headers = item.headers || {};\nconst sigHeader = headers['elevenlabs-signature'] || headers['ElevenLabs-Signature'] || '';\nconst parts = Object.fromEntries(sigHeader.split(',').map(p => { const i = p.indexOf('='); return [p.slice(0,i).trim(), p.slice(i+1).trim()]; }));\nconst t = parts.t || '';\nconst v0 = parts.v0 || '';\nconst ageSec = t ? Math.abs(Math.floor(Date.now() / 1000) - Number(t)) : null;\nconst stale = !t || !v0 || !Number.isFinite(ageSec) || ageSec > 60 * 30;\nconst body = JSON.stringify(item.body ?? {});\nreturn [{ json: { ...item, _hmac_t: t, _hmac_v0: v0, _hmac_payload: t + '.' + body, _hmac_stale: stale, _hmac_age: ageSec } }];"
      }
    },
    {
      "id": "hmac-compute",
      "name": "Verify HMAC: compute",
      "type": "n8n-nodes-base.crypto",
      "typeVersion": 1,
      "position": [
        696,
        304
      ],
      "parameters": {
        "action": "hmac",
        "type": "SHA256",
        "value": "={{ $json._hmac_payload }}",
        "secret": "<REDACTED:elevenlabs-webhook-secret>",
        "encoding": "hex",
        "dataPropertyName": "_hmac_expected"
      }
    },
    {
      "id": "hmac-branch",
      "name": "HMAC: ok?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        916,
        304
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "h1",
              "leftValue": "={{ !$json._hmac_stale }}",
              "rightValue": "true",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            },
            {
              "id": "h2",
              "leftValue": "={{ $json._hmac_v0 }}",
              "rightValue": "={{ $json._hmac_expected }}",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ]
        },
        "options": {}
      }
    },
    {
      "id": "hmac-reject",
      "name": "HMAC: reject",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        1136,
        424
      ],
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ { error: \"UNAUTHORIZED\", message: $json._hmac_stale ? \"stale_or_missing_signature\" : \"signature_mismatch\" } }}",
        "options": {
          "responseCode": 401
        }
      }
    }
  ],
  "connections": {
    "Check Event Type": {
      "main": [
        [
          {
            "node": "Parse Call Data",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Parse Failure Data",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Log Unknown Event",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Call Data": {
      "main": [
        [
          {
            "node": "Route CRM Decision",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Route CRM Decision": {
      "main": [
        [
          {
            "node": "Add Note to Pipedrive",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Log Missing ID",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Update Person Status": {
      "main": [
        [
          {
            "node": "Respond Success",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Log Missing ID": {
      "main": [
        [
          {
            "node": "Respond No ID",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Failure Data": {
      "main": [
        [
          {
            "node": "Respond Failure Logged",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "ElevenLabs Webhook": {
      "main": [
        [
          {
            "node": "Verify HMAC: parse",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Correlation ID": {
      "main": [
        [
          {
            "node": "Validate Payload",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Validate Payload": {
      "main": [
        [
          {
            "node": "Route Validation",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Route Validation": {
      "main": [
        [
          {
            "node": "Respond Validation Error",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Circuit Breaker Check",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Log Unknown Event": {
      "main": [
        [
          {
            "node": "Respond Unknown Event",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Retry Handler": {
      "main": [
        [
          {
            "node": "Route Retry Result",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Route Retry Result": {
      "main": [
        [
          {
            "node": "Update Person Status",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Respond Retry Exhausted",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Circuit Breaker Check": {
      "main": [
        [
          {
            "node": "Route Circuit",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Route Circuit": {
      "main": [
        [
          {
            "node": "Check Event Type",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Respond Circuit Open",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Add Note to Pipedrive": {
      "main": [
        [
          {
            "node": "Retry Handler",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Retry Handler",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Pipedrive API Wrapper": {
      "main": [
        [
          {
            "node": "Retry Handler",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Verify HMAC: parse": {
      "main": [
        [
          {
            "node": "Verify HMAC: compute",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Verify HMAC: compute": {
      "main": [
        [
          {
            "node": "HMAC: ok?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HMAC: ok?": {
      "main": [
        [
          {
            "node": "Extract Correlation ID",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "HMAC: reject",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1",
    "callerPolicy": "workflowsFromSameOwner",
    "availableInMCP": false,
    "saveDataErrorExecution": "all",
    "saveDataSuccessExecution": "all",
    "saveManualExecutions": true,
    "saveExecutionProgress": true
  },
  "tags": [
    "DEV"
  ]
}
Pro

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

About this workflow

[DEV] post-call / pipedrive-updater. Uses pipedrive, crypto. Webhook trigger; 28 nodes.

Source: https://github.com/wranngle/n8n_showcase/blob/main/workflows/post-call/pipedrive-updater.json — original creator credit. Request a take-down →

More CRM & Sales workflows → · Browse all categories →

Related workflows

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

CRM & Sales

v25.1.1. Uses httpRequest, mySql, n8n-nodes-zohozeptomail. Webhook trigger; 80 nodes.

HTTP Request, MySQL, N8N Nodes Zohozeptomail
CRM & Sales

Pipedrive:Activity:create get update getAll delete:Deal:create get update duplicate getAll delete:Person:create get update getAll search delete:File:create get download delete:Organization:create get

Start, Pipedrive, Move Binary Data
CRM & Sales

This is ideal for agencies, freelancers, SaaS founders, and small sales teams who want every lead recorded and followed up automatically within seconds. The workflow supports two storage options: HubS

HTTP Request, Google Sheets, Slack +1
CRM & Sales

This template enriches HubSpot company records using real-time data from the Bedrijfsdata.nl API. It listens for changes to company properties (e.g., ) and updates records with verified information su

HubSpot, @Bedrijfsdatanl/N8N Nodes Bedrijfsdata
CRM & Sales

This workflow receives inbound leads via webhook, uses Anthropic Claude (with website scraping and web search) to score and classify them, then updates HubSpot lifecycle stage, creates deals for hot l

HTTP Request, HubSpot, Slack +1