n8n
Use n8n's built-in Webhook and HTTP Request nodes — no community node needed.
Receive events
Add a Webhook node: method
POST, path e.g.essere-events. Copy the production URL (https://your-n8n.example.com/webhook/essere-events).Register it:
curl -X POST https://voice-public-api.essere.ai/v1/webhook-endpoints \ -H "Authorization: Bearer $ESSERE_API_KEY" \ -H "Content-Type: application/json" \ -d '{"url": "https://your-n8n.example.com/webhook/essere-events", "events": ["call.completed", "lead.captured"], "description": "n8n"}'Save the returned
secret(whsec_...) as an n8n credential/variable.(Recommended) Verify the signature in a Code node right after the webhook. Set the Webhook node's Raw body option ON, then:
// n8n Code node (Run Once for Each Item) const crypto = require('crypto'); const secret = $env.ESSERE_WEBHOOK_SECRET; // whsec_... const header = $json.headers['x-essere-signature'] || ''; const raw = Buffer.from($json.body, 'base64'); // raw body (base64 when Raw Body is on) let t = null; const v1s = []; // rotation overlap: the header may carry TWO v1 entries for (const part of header.split(',')) { const i = part.indexOf('='); if (part.slice(0, i) === 't') t = Number(part.slice(i + 1)); else if (part.slice(0, i) === 'v1') v1s.push(part.slice(i + 1)); } if (!t || Math.abs(Date.now() / 1000 - t) > 300) { throw new Error('stale or missing signature timestamp'); } const expected = crypto.createHmac('sha256', secret).update(`${t}.`).update(raw).digest('hex'); const ok = v1s.some(v1 => { const b = Buffer.from(v1); return b.length === Buffer.from(expected).length && crypto.timingSafeEqual(Buffer.from(expected), b); }); if (!ok) throw new Error('bad signature'); return { json: JSON.parse(raw.toString()) };Branch on
{{$json.type}}(e.g. a Switch node) and routecall.completedevents into your CRM, Slack, database, etc.
Call the API
Add an HTTP Request node:
- Authentication: Generic → Header Auth; create a credential with
name
Authorizationand valueBearer <your API key>. - List calls: GET
https://voice-public-api.essere.ai/v1/calls?limit=50 - Fetch a transcript: GET
https://voice-public-api.essere.ai/v1/calls/{{$json.data.id}}/transcript - Create a contact: POST
https://voice-public-api.essere.ai/v1/contactswith JSON body{"phone": "{{$json.phone}}", "name": "{{$json.name}}"}
For list endpoints, enable the node's Pagination option: type
"Response contains next URL"? No — use Update a parameter in each request
with parameter cursor set from {{$response.body.next_cursor}}, stopping
when {{$response.body.has_more}} is false.