Zum Inhalt springen
sonopolarHandbuch sonopolar.com
Inhalt

Verwaltung für Admins

Schnittstelle

Über die Schnittstelle binden Sie sonopolar an ein CRM oder ein eigenes Programm an: Nebenstellen und Anrufe lesen, für eine Nebenstelle wählen lassen (Click-to-Dial) und Anrufereignisse als Webhook empfangen. Jede Anfrage sieht nur Ihr Unternehmen.

Schlüssel und Webhooks legen Sie unter Verwaltung > Schnittstelle an.

API-Schlüssel

  1. Unter API-Schlüssel eine Bezeichnung eintragen, etwa CRM.
  2. Schlüssel anlegen.
  3. Den Schlüssel sofort kopieren. Er beginnt mit sp_live_ und wird nur dieses eine Mal angezeigt.

Die Liste zeigt je Schlüssel den Anfang zur Wiedererkennung und Zuletzt benutzt. Widerrufen macht einen Schlüssel sofort ungültig.

Ein Schlüssel hat alle Rechte Ihres Unternehmens an der Schnittstelle. Rechte je Schlüssel (nur lesen, nur wählen) gibt es nicht. Legen Sie je Programm einen eigenen Schlüssel an, dann können Sie einzelne widerrufen.

Anfragen

Die Schnittstelle liegt unter der Adresse Ihres Portals, Pfad /api/v1/. Jede Anfrage trägt den Schlüssel als Bearer-Token. In den Beispielen steht firma.sonopolar.com für die Adresse Ihres Portals.

curl -H "Authorization: Bearer $SONOPOLAR_KEY" https://firma.sonopolar.com/api/v1/me

Antworten sind JSON. Fehler kommen als { "statusCode", "message" }:

Status Bedeutung
400 falsche Eingabe, die message nennt das Feld
401 Schlüssel fehlt oder ist ungültig
404 gehört nicht zu Ihrem Unternehmen oder gibt es nicht
409 die Nebenstelle hat kein angemeldetes Telefon
429 zu viele Anfragen

Grenzen: 60 Anfragen je Minute und Schlüssel. Listen liefern höchstens 200 Einträge.

Endpunkte

GET /api/v1/me

Ihr Unternehmen.

{ "tenantId": "68ecb3b7-…", "name": "Beispiel GmbH" }

GET /api/v1/extensions

Alle Nebenstellen mit Präsenz, ohne Passwörter.

[
  { "number": "1001", "name": "Anna Berger", "email": "a.berger@example.org", "role": "admin", "active": true,
    "status": "available", "statusText": null, "dnd": false, "presence": "available" }
]

presence ist, was die Anlage sieht: offline (kein Telefon angemeldet oder Nebenstelle abgeschaltet), available, ringing, busy. status ist der selbst gesetzte Zustand: available, away, dnd oder custom mit statusText.

GET /api/v1/calls?from=&to=&limit=

Abgeschlossene Anrufe, neueste zuerst. from und to als ISO-8601-Zeit auf den Beginn, limit 1 bis 200, Vorgabe 50.

curl -H "Authorization: Bearer $SONOPOLAR_KEY" \
  "https://firma.sonopolar.com/api/v1/calls?from=2026-10-01T00:00:00Z&limit=20"
[
  { "callUuid": "e024cca4-…", "direction": "inbound", "fromNumber": "+4930123456", "toNumber": "1002",
    "startedAt": "2026-10-12T09:58:04.445Z", "answeredAt": "2026-10-12T09:58:08.544Z",
    "endedAt": "2026-10-12T10:01:09.424Z", "durationSec": 181, "hangupCause": "NORMAL_CLEARING" }
]

direction: inbound, outbound, internal. Ein unbeantworteter Anruf hat answeredAt: null. hangupCause ist die technische Ursache des Gesprächsendes.

GET /api/v1/calls/active

Laufende Kanäle. Ein internes Gespräch hat zwei Kanäle (Anrufer und Gerufener), ein eingehender Anruf über eine Rufnummer ebenso.

[
  { "callUuid": "4e290b2d-…", "direction": "inbound", "fromNumber": "+4930123456", "toNumber": "1002",
    "extensionNumber": "1002", "startedAt": "2026-10-12T10:12:43.295Z", "answered": false, "elapsedSec": 3 }
]

POST /api/v1/calls

Wählen für eine Nebenstelle. Erst klingelt ihr Telefon; nach dem Abheben ruft die Anlage die Nummer, als hätte das Telefon sie gewählt, mit der Rufnummer der Nebenstelle als Absender.

curl -H "Authorization: Bearer $SONOPOLAR_KEY" -H "content-type: application/json" \
  -d '{"extension":"1001","number":"+49 30 1234567"}' https://firma.sonopolar.com/api/v1/calls

Antwort 202:

{ "callUuid": "0d1469eb-…", "extension": "1001", "number": "+49301234567" }

number darf Leerzeichen, Klammern, Striche und Punkte enthalten. callUuid ist der Kanal zum Telefon der Nebenstelle; die Ereignisse dazu kommen per Webhook. Die Antwort bestätigt nur den Start. Ob das Telefon abhebt und die Zielnummer erreichbar ist, entscheidet sich danach. 404: Nebenstelle unbekannt oder abgeschaltet. 409: kein Telefon angemeldet.

GET /api/v1/contacts?q=

Die Adressbücher aller Nebenstellen (eigene Kontakte, Microsoft 365, Google). extensionNumber nennt, wem der Kontakt gehört. q sucht in Name und Firma. Höchstens 200 Treffer.

[
  { "id": "5bb39675-…", "source": "local", "displayName": "Max Mustermann", "company": "Beispiel AG",
    "phones": [{ "label": "work", "value": "+49 30 111" }], "emails": ["max@example.org"], "extensionNumber": "1001" }
]

GET /api/v1/webhooks

Die Webhooks Ihres Unternehmens ohne Geheimnis: id, url, events, active, createdAt, lastDeliveryAt, lastStatus.

POST /api/v1/webhooks/test

Schickt ping an einen Webhook ({ "id": "…" }) oder ohne Rumpf an alle aktiven, ohne Wiederholung. Die Antwort nennt je Webhook den HTTP-Status des Empfängers, 0 wenn er nicht antwortet.

[{ "id": "9f02d267-…", "status": 200 }]

Webhooks

Unter Webhooks legen Sie an:

  • Adresse (https). Nur https, außer für localhost.
  • Ereignisse: Anruf beginnt, Anruf angenommen, Anruf beendet, Anruf verpasst. Mindestens eines.

Webhook anlegen zeigt das Geheimnis für die Signaturprüfung genau einmal. Je Webhook gibt es Aktivieren und Deaktivieren, Testen (zeigt die Antwort des Empfängers), Letzte Zustellung und Löschen.

Ereignisse

Ereignis Wann
call.started ein Kanal entsteht: es klingelt oder wird gewählt
call.answered ein Kanal wird angenommen
call.ended ein Kanal endet, immer; durationSec ab Annahme, null ohne Annahme
call.missed zusätzlich zu call.ended, wenn ein eingehender oder interner Kanal ohne Annahme endet
ping Testzustellung, ohne call

Ereignisse kommen je Kanal, nicht je Gespräch. Ein internes Gespräch meldet zwei call.started (Anrufer und Gerufener), ein Anruf in eine Rufgruppe eines je gerufenem Telefon. callUuid unterscheidet die Kanäle, extensionNumber nennt die Nebenstelle am Kanal, soweit erkennbar.

Zustellung

POST mit JSON, Zeitlimit 5 Sekunden. Jede Antwort außer 2xx gilt als Fehler, dann folgen zwei Wiederholungen nach 10 und 60 Sekunden mit derselben X-Sonopolar-Delivery. Reihenfolge und Einmaligkeit sind nicht garantiert. Prüfen Sie X-Sonopolar-Delivery gegen schon Gesehenes.

Kopfzeilen:

Content-Type: application/json
X-Sonopolar-Event: call.started
X-Sonopolar-Delivery: a003c293-b463-4b7b-8892-cc6f301d9063
X-Sonopolar-Signature: sha256=<HMAC-SHA256 des Rumpfs mit dem Geheimnis, hex>

Rumpf:

{
  "event": "call.ended",
  "at": "2026-10-12T10:12:46.416Z",
  "tenantId": "68ecb3b7-…",
  "call": {
    "callUuid": "4e290b2d-…",
    "direction": "inbound",
    "fromNumber": "+4930123456",
    "toNumber": "1002",
    "extensionNumber": "1002",
    "durationSec": 42
  }
}

Signatur prüfen

Beispiel mit Node und Express. Die Signatur gilt für den Rumpf Byte für Byte; lesen Sie ihn deshalb roh und erst nach der Prüfung als JSON.

const { createHmac, timingSafeEqual } = require('node:crypto');
const express = require('express');

const SECRET = process.env.SONOPOLAR_WEBHOOK_SECRET;
const app = express();

app.post('/sonopolar', express.raw({ type: 'application/json' }), (req, res) => {
  const given = req.header('x-sonopolar-signature') ?? '';
  const expected = 'sha256=' + createHmac('sha256', SECRET).update(req.body).digest('hex');
  const ok = given.length === expected.length && timingSafeEqual(Buffer.from(given), Buffer.from(expected));
  if (!ok) return res.sendStatus(401);
  const { event, call } = JSON.parse(req.body);
  if (event === 'call.started' && call.direction === 'inbound') {
    // Kontakt zu call.fromNumber im CRM öffnen, für call.extensionNumber anzeigen
  }
  res.sendStatus(204);
});

app.listen(3000);

Grenzen

  • Wird die Anlage neu gestartet, gehen noch ausstehende Wiederholungen verloren.
  • Ein Kanal, der binnen Millisekunden wieder endet (etwa ohne Ziel), meldet keine Ereignisse.
  • extensionNumber erkennt die Anlage an der Form der Nummer. Auch die Nummer einer Rufgruppe oder eines Menüs kann dort stehen.