{
  "name": "Vocra — call log to Google Sheets",
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "vocra-call-log",
        "options": { "rawBody": true }
      },
      "id": "webhook",
      "name": "Vocra webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [0, 0],
      "webhookId": "vocra-call-log"
    },
    {
      "parameters": {
        "jsCode": "// Verify the delivery really came from Vocra before acting on it.\n// The webhook URL is public; without this check anyone who learns it can post\n// fabricated call results into your spreadsheet.\nconst crypto = require('crypto');\n\nconst SECRET = 'VOCRA_WEBHOOK_SECRET'; // Dashboard -> Webhooks\nconst TOLERANCE_SECONDS = 300;\n\nconst headers = $input.first().json.headers ?? {};\nconst raw = $input.first().json.body;\nconst body = typeof raw === 'string' ? raw : JSON.stringify(raw);\n\nconst header = headers['x-vocra-signature'] ?? '';\nconst parts = Object.fromEntries(\n  header.split(',').map((p) => p.trim().split('='))\n);\n\nconst timestamp = Number.parseInt(parts.t ?? '', 10);\nif (!Number.isFinite(timestamp)) throw new Error('Missing signature timestamp');\n\n// Reject an old delivery: without this a captured request could be replayed.\nconst age = Math.abs(Math.floor(Date.now() / 1000) - timestamp);\nif (age > TOLERANCE_SECONDS) throw new Error('Signature too old');\n\nconst expected = crypto\n  .createHmac('sha256', SECRET)\n  .update(`${timestamp}.${body}`)\n  .digest('hex');\n\nif (expected !== parts.v1) throw new Error('Bad signature');\n\nreturn [{ json: typeof raw === 'string' ? JSON.parse(raw) : raw }];"
      },
      "id": "verify",
      "name": "Verify signature",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [220, 0]
    },
    {
      "parameters": {
        "conditions": {
          "options": { "caseSensitive": true, "version": 2 },
          "conditions": [
            {
              "leftValue": "={{ $json.event }}",
              "rightValue": "call_analyzed",
              "operator": { "type": "string", "operation": "equals" }
            }
          ],
          "combinator": "and"
        }
      },
      "id": "filter",
      "name": "Only call_analyzed",
      "type": "n8n-nodes-base.filter",
      "typeVersion": 2,
      "position": [440, 0],
      "notes": "All three events carry the same call_id. Without this filter the workflow runs three times per call and writes two half-empty rows."
    },
    {
      "parameters": {
        "operation": "append",
        "documentId": "YOUR_SHEET_ID",
        "sheetName": "Calls",
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "call_id": "={{ $json.call.call_id }}",
            "when": "={{ $json.call.started_at }}",
            "from": "={{ $json.call.from_number }}",
            "to": "={{ $json.call.to_number }}",
            "duration_s": "={{ $json.call.duration_s }}",
            "end_reason": "={{ $json.call.end_reason }}",
            "summary": "={{ $json.call.analysis?.summary }}",
            "sentiment": "={{ $json.call.analysis?.sentiment }}",
            "successful": "={{ $json.call.analysis?.call_successful }}",
            "custom": "={{ JSON.stringify($json.call.analysis?.custom_analysis_data ?? {}) }}"
          }
        }
      },
      "id": "sheet",
      "name": "Append to sheet",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4,
      "position": [660, 0]
    }
  ],
  "connections": {
    "Vocra webhook": { "main": [[{ "node": "Verify signature", "type": "main", "index": 0 }]] },
    "Verify signature": { "main": [[{ "node": "Only call_analyzed", "type": "main", "index": 0 }]] },
    "Only call_analyzed": { "main": [[{ "node": "Append to sheet", "type": "main", "index": 0 }]] }
  },
  "settings": { "executionOrder": "v1" },
  "pinData": {}
}
