{
 "name": "FRANZFON Anrufe in EspoCRM",
 "nodes": [
  {
   "id": "1",
   "name": "FRANZFON Webhook",
   "type": "n8n-nodes-base.webhook",
   "typeVersion": 2,
   "position": [
    0,
    0
   ],
   "webhookId": "franzfon-crm",
   "parameters": {
    "httpMethod": "POST",
    "path": "franzfon-crm",
    "responseMode": "responseNode",
    "options": {
     "rawBody": true
    }
   }
  },
  {
   "id": "2",
   "name": "Signatur prüfen",
   "type": "n8n-nodes-base.code",
   "typeVersion": 2,
   "position": [
    220,
    0
   ],
   "parameters": {
    "jsCode": "const crypto = require('crypto');\n// Webhook-Geheimnis aus FRANZFON (System > Anruf-Webhooks > Geheimnis > Anzeigen), Format whsec_<kennung>.<schlüssel>\nconst WHSEC = 'whsec_HIER_DEIN_GEHEIMNIS';\nconst cut = WHSEC.indexOf('.', 6);\nconst KEY_ID = WHSEC.slice(6, cut), SECRET = Buffer.from(WHSEC.slice(cut + 1), 'base64');\nconst item = $input.first();\nconst h = item.json.headers || {};\nconst raw = await this.helpers.getBinaryDataBuffer(0, 'data');\nconst ts = h['x-franzfon-timestamp'], id = h['x-franzfon-event-id'], sig = h['x-franzfon-signature'] || '';\nconst expected = 'v1=' + crypto.createHmac('sha256', SECRET).update(ts + '.' + id + '.').update(raw).digest('hex');\nconst valid = h['x-franzfon-key-id'] === KEY_ID && sig.length === expected.length\n  && crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected)) && Math.abs(Date.now() / 1000 - Number(ts)) <= 300;\nconst event = valid ? JSON.parse(raw.toString('utf8')) : null;\nreturn [{ json: { valid, event } }];"
   }
  },
  {
   "id": "3",
   "name": "Signatur gültig?",
   "type": "n8n-nodes-base.if",
   "typeVersion": 2,
   "position": [
    440,
    0
   ],
   "parameters": {
    "conditions": {
     "options": {
      "caseSensitive": true,
      "typeValidation": "loose"
     },
     "combinator": "and",
     "conditions": [
      {
       "leftValue": "={{ $json.valid }}",
       "rightValue": true,
       "operator": {
        "type": "boolean",
        "operation": "true",
        "singleValue": true
       }
      }
     ]
    }
   }
  },
  {
   "id": "4",
   "name": "Antwort 204",
   "type": "n8n-nodes-base.respondToWebhook",
   "typeVersion": 1.1,
   "position": [
    660,
    -100
   ],
   "parameters": {
    "respondWith": "noData",
    "options": {
     "responseCode": 204
    }
   }
  },
  {
   "id": "5",
   "name": "Antwort 401",
   "type": "n8n-nodes-base.respondToWebhook",
   "typeVersion": 1.1,
   "position": [
    660,
    100
   ],
   "parameters": {
    "respondWith": "noData",
    "options": {
     "responseCode": 401
    }
   }
  },
  {
   "id": "6",
   "name": "Anruf abgeschlossen?",
   "type": "n8n-nodes-base.if",
   "typeVersion": 2,
   "position": [
    880,
    -100
   ],
   "parameters": {
    "conditions": {
     "options": {
      "caseSensitive": true,
      "typeValidation": "loose"
     },
     "combinator": "and",
     "conditions": [
      {
       "leftValue": "={{ $('Signatur prüfen').first().json.event.type }}",
       "rightValue": "call.completed",
       "operator": {
        "type": "string",
        "operation": "equals"
       }
      }
     ]
    }
   }
  },
  {
   "id": "7",
   "name": "Kontakt im CRM suchen",
   "type": "n8n-nodes-base.httpRequest",
   "typeVersion": 4.2,
   "position": [
    1100,
    -100
   ],
   "parameters": {
    "url": "https://dein-crm.example/api/v1/Contact",
    "sendQuery": true,
    "queryParameters": {
     "parameters": [
      {
       "name": "where[0][type]",
       "value": "equals"
      },
      {
       "name": "where[0][attribute]",
       "value": "phoneNumber"
      },
      {
       "name": "where[0][value]",
       "value": "={{ $('Signatur prüfen').first().json.event.direction === 'incoming' ? $('Signatur prüfen').first().json.event.data.from.number : $('Signatur prüfen').first().json.event.data.to.number }}"
      },
      {
       "name": "select",
       "value": "id,name,accountId"
      }
     ]
    },
    "sendHeaders": true,
    "headerParameters": {
     "parameters": [
      {
       "name": "X-Api-Key",
       "value": "HIER_DEIN_ESPOCRM_API_SCHLUESSEL"
      }
     ]
    },
    "options": {}
   }
  },
  {
   "id": "8",
   "name": "Anruf aufbereiten",
   "type": "n8n-nodes-base.code",
   "typeVersion": 2,
   "position": [
    1320,
    -100
   ],
   "parameters": {
    "jsCode": "// Anruf für das CRM aufbereiten: Gegenstelle, Richtung, Ergebnis, Zeiten\n// Zuständiger CRM-Benutzer: EspoCRM verlangt ihn. Hier fest, im Betrieb je Nebenstelle zuordnen.\nconst CRM_USER_ID = 'HIER_ID_DES_CRM_BENUTZERS';\nconst e = $('Signatur prüfen').first().json.event;\nconst d = e.data || {};\nconst incoming = e.direction === 'incoming';\nconst other = incoming ? (d.from || {}).number : (d.to || {}).number;\nconst found = ($input.first().json.list || [])[0] || null;\nconst t = s => s ? s.replace('T', ' ').slice(0, 19) : null;\nconst outcome = { answered: 'angenommen', missed: 'verpasst', voicemail: 'Nachricht hinterlassen', busy: 'besetzt', failed: 'fehlgeschlagen', not_reached: 'nicht erreicht' }[d.outcome] || d.outcome;\nconst who = d.actor ? (d.actor.name || d.actor.extension) : (d.ring_group ? d.ring_group.name : null);\nreturn [{ json: {\n  name: (incoming ? 'Anruf von ' : 'Anruf an ') + (found ? found.name : (other || 'unbekannt')) + ' (' + outcome + ')',\n  status: d.outcome === 'answered' ? 'Held' : 'Not Held',\n  direction: incoming ? 'Inbound' : 'Outbound',\n  dateStart: t(d.started_at), dateEnd: t(d.ended_at),\n  description: 'FRANZFON: ' + outcome + (who ? ', ' + who : '') + ', Dauer ' + (d.duration_seconds || 0) + ' s' + (d.voicemail_left ? ', Voicemail hinterlassen' : '') + '. Nummer ' + (other || 'unbekannt') + '.',\n  assignedUserId: CRM_USER_ID,\n  contactsIds: found ? [found.id] : [],\n  parentType: found && found.accountId ? 'Account' : null, parentId: found ? found.accountId || null : null\n} }];"
   }
  },
  {
   "id": "9",
   "name": "Anruf im CRM anlegen",
   "type": "n8n-nodes-base.httpRequest",
   "typeVersion": 4.2,
   "position": [
    1540,
    -100
   ],
   "parameters": {
    "method": "POST",
    "url": "https://dein-crm.example/api/v1/Call",
    "sendHeaders": true,
    "headerParameters": {
     "parameters": [
      {
       "name": "X-Api-Key",
       "value": "HIER_DEIN_ESPOCRM_API_SCHLUESSEL"
      }
     ]
    },
    "sendBody": true,
    "specifyBody": "json",
    "jsonBody": "={{ JSON.stringify($json) }}",
    "options": {}
   }
  }
 ],
 "connections": {
  "FRANZFON Webhook": {
   "main": [
    [
     {
      "node": "Signatur prüfen",
      "type": "main",
      "index": 0
     }
    ]
   ]
  },
  "Signatur prüfen": {
   "main": [
    [
     {
      "node": "Signatur gültig?",
      "type": "main",
      "index": 0
     }
    ]
   ]
  },
  "Signatur gültig?": {
   "main": [
    [
     {
      "node": "Antwort 204",
      "type": "main",
      "index": 0
     }
    ],
    [
     {
      "node": "Antwort 401",
      "type": "main",
      "index": 0
     }
    ]
   ]
  },
  "Antwort 204": {
   "main": [
    [
     {
      "node": "Anruf abgeschlossen?",
      "type": "main",
      "index": 0
     }
    ]
   ]
  },
  "Anruf abgeschlossen?": {
   "main": [
    [
     {
      "node": "Kontakt im CRM suchen",
      "type": "main",
      "index": 0
     }
    ],
    []
   ]
  },
  "Kontakt im CRM suchen": {
   "main": [
    [
     {
      "node": "Anruf aufbereiten",
      "type": "main",
      "index": 0
     }
    ]
   ]
  },
  "Anruf aufbereiten": {
   "main": [
    [
     {
      "node": "Anruf im CRM anlegen",
      "type": "main",
      "index": 0
     }
    ]
   ]
  }
 },
 "settings": {
  "executionOrder": "v1"
 }
}