Klant-API
Met de klant-API bestelt en beheert je eigen software mobiele abonnementen met eSIM op je VoiceOne-account. Een nieuwe medewerker krijgt zo automatisch een zakelijk 06-nummer, een verloren telefoon blokkeer je meteen en een vertrokken medewerker zeg je op. Hoe het ervoor staat, hoor je via webhooks.
De API staat per account aan. Wil je hem gebruiken, mail dan [email protected]. Daarna maak je zelf sleutels aan in One, onder Mijn account → API. Daar beheer je ook je webhooks en zie je het logboek van alle aanroepen.
Op deze pagina
Snel starten
- Vraag via [email protected] of VoiceOne de sandbox voor je account aanzet.
- Maak in One een sandbox-sleutel aan (
vo_test_…). Je ziet hem één keer, dus bewaar hem meteen veilig. - Haal de producten op die je mag bestellen:
curl https://api.voiceone.nl/v1/products \
-H "Authorization: Bearer vo_test_…"- Bestel een abonnement. Geef bij elke POST een eigen
Idempotency-Keymee:
curl https://api.voiceone.nl/v1/mobile-subscriptions \
-H "Authorization: Bearer vo_test_…" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: new-advisor-42" \
-d '{
"product": "your_product_code",
"user": { "first_name": "Jan", "last_name": "Jansen", "email": "[email protected]" },
"external_reference": "advisor-42"
}'- In de sandbox is het abonnement na ongeveer een minuut actief. Volg het met een webhook, of vraag het op met
GET /v1/mobile-subscriptions/{id}. - Werkt alles? Vraag dan live aan. Een live-sleutel (
vo_live_…) werkt alleen vanaf de IP-adressen die je bij de sleutel opgeeft.
Basis
Adres, sleutel en formaat
- Adres:
https://api.voiceone.nl/v1. Alleen via https; http geeft 403. - Sleutel: stuur hem mee als
Authorization: Bearer <sleutel>. Een sleutel begint metvo_live_(live) ofvo_test_(sandbox), gevolgd door 40 letters en cijfers. De sleutel bepaalt de omgeving. VoiceOne bewaart alleen een hash: kwijt is een nieuwe aanmaken. - Formaat: JSON in UTF-8 met
Content-Type: application/json, hooguit 64 kB per verzoek. Bedragen zijn strings met twee decimalen, in euro en exclusief btw. Tijden staan in ISO 8601 met tijdzone (Europe/Amsterdam), datums alsYYYY-MM-DDen telefoonnummers in E.164 (+31612345678). - Request-id: elk antwoord heeft de header
X-Request-Id. Noem die als je een vraag hebt over een aanroep. - Server naar server: de API kent geen CORS. Roep hem aan vanaf je server, nooit vanuit een browser of app: daar zou je sleutel voor iedereen leesbaar zijn.
- Nieuwe velden en events kunnen erbij komen. Laat je systeem onbekende velden negeren.
Rechten per sleutel
Bij het aanmaken kies je welke rechten een sleutel krijgt. Geef een sleutel alleen wat je systeem nodig heeft.
- subscriptions:read
- Abonnementen en producten lezen.
- subscriptions:write
- Bestellen, wijzigen, blokkeren, deblokkeren, opzeggen, een nieuwe eSIM en een SIP-kanaal aanvragen. Hier zijn kosten aan verbonden.
- esim:read
- De activatiecode en bevestigingscode van een eSIM in het antwoord. Zonder dit recht zijn die velden
null. - sip:read
- De inloggegevens van een SIP-kanaal ophalen.
- calls:read
- Nog niet in gebruik (gesprekken, in een volgende versie).
Webhooks bevatten de eSIM-codes nooit, ook niet met esim:read. Haal ze op met GET /v1/mobile-subscriptions/{id} zodra esim.ready waar is.
Limieten
- 120 aanroepen per minuut per sleutel. Daarboven 429
rate_limitedmet de headerRetry-After(seconden). - Veel aanroepen met een ongeldige sleutel vanaf één IP-adres: 429 voor tien minuten.
- Live alleen vanaf vaste IP-adressen: maximaal 20 adressen of reeksen per sleutel, IPv4 of IPv6, reeksen niet groter dan /16 (IPv4) of /32 (IPv6). De sandbox mag zonder.
- Daglimiet op nieuwe abonnementen per account (standaard 10, af te spreken met VoiceOne). Daarboven wordt de bestelling gewoon aangenomen, met status
awaiting_approval. VoiceOne zet hem na controle door. - Vervaldatum: een sleutel kan een einddatum hebben. Daarna geeft hij 401.
Idempotentie: nooit dubbel
- Elke POST vraagt de header
Idempotency-Key: 1 tot 200 tekens, letters, cijfers en. _ : -. Gebruik een nieuwe per handeling, bijvoorbeeld het id uit je eigen systeem. - Dezelfde Idempotency-Key met dezelfde body binnen 7 dagen geeft precies hetzelfde antwoord terug, met de header
Idempotent-Replayed: true. Er wordt niets opnieuw uitgevoerd, dus na een time-out kun je veilig opnieuw proberen. - Dezelfde Idempotency-Key met een andere body, of vanaf een andere API-sleutel: 409
idempotency_conflict. Loopt het eerste verzoek nog: 409request_in_progressmetRetry-After. - Een 5xx, een 409
busyen een 429 worden niet bewaard: probeer het dan gerust opnieuw met dezelfde Idempotency-Key. - Is een bestelling eenmaal geplaatst, dan krijgt dat verzoek nooit meer een 5xx maar een 202.
Lijsten
GET /v1/mobile-subscriptions geeft de nieuwste eerst, standaard 50 per keer. Gebruik ?limit= (1–100) en voor de volgende pagina ?cursor= met de waarde van next_cursor. Filteren kan op ?status= en ?external_reference=. De lijst bevat alle mobiele abonnementen van je account, ook de abonnementen die niet via de API zijn besteld.
{
"data": [ { "object": "mobile_subscription", "id": "ms_1234", … } ],
"has_more": true,
"next_cursor": "ms_1234"
}Fouten
Een fout heeft altijd deze vorm. details staat er alleen bij als er iets te melden is, zoals het veld dat niet klopt.
{
"error": {
"code": "validation_error",
"message": "user.last_name is required (1-60 characters).",
"details": { "field": "user.last_name" },
"request_id": "HrRbWgZ9J6Erbn70"
}
}Laat je systeem reageren op code, niet op message: de tekst kan veranderen.
- 400 invalid_
request - Geen geldige JSON, geen JSON-object, of een body groter dan 64 kB.
- 401 unauthorized
- Geen sleutel, of een onbekende, ingetrokken of verlopen sleutel.
- 403 api_
disabled - De API, of deze omgeving, staat voor je account uit.
- 403 ip_
not_ allowed - Het IP-adres staat niet bij de sleutel.
- 403 insufficient_
scope - De sleutel mist het recht (
details.required). - 403 account_
blocked - Je account is geblokkeerd, bijvoorbeeld door een betaalachterstand.
- 403 forbidden
- Deze aanroep kan niet met deze sleutel, zoals
sandbox/simulatemet een live-sleutel. - 403 webrtc_
disabled - Bellen in de browser staat voor je account niet aan.
- 404 not_
found - Onbekend endpoint, of een abonnement dat niet bestaat of bij een ander account hoort.
- 405 method_
not_ allowed - Verkeerde methode. De header
Allownoemt de goede. - 409 busy
- Er loopt al een actie op deze simkaart of dit account. Probeer het zo opnieuw.
- 409 invalid_
state - De actie past niet bij de stand, zoals deblokkeren terwijl hij niet geblokkeerd is.
detailsgeeft vaak de huidigestatus. - 409 not_
supported - Niet mogelijk voor dit product (een SIP-kanaal kan alleen bij Mobiel Compleet).
- 409 idempotency_
conflict - Deze Idempotency-Key is al gebruikt voor een ander verzoek.
- 409 request_
in_ progress - Een verzoek met deze Idempotency-Key loopt nog.
- 422 validation_
error - Ongeldige invoer.
details.fieldnoemt het veld. - 422 product_
not_ allowed - Dit product is niet vrijgegeven voor je account (zie
GET /v1/products). - 422 idempotency_
key_ required - POST zonder
Idempotency-Key. - 429 rate_
limited - Te veel aanroepen. Wacht het aantal seconden uit
Retry-After. - 500 internal
- Onverwachte fout. Mail de
request_idnaar [email protected]. - 503 unavailable
- Een onderdeel is tijdelijk niet beschikbaar. Probeer het later opnieuw.
Endpoints
Bij elke POST hoort een Idempotency-Key. Het {id} van een abonnement begint met ms_ (in de sandbox ms_test_).
esim:read).subscriptions:readProducten
{
"data": [
{
"object": "product",
"code": "your_product_code",
"name": "Mobiel Compleet",
"type": "mobile_subscription",
"price_excl": "x.xx",
"period": "month",
"setup_fee_excl": "x.xx",
"currency": "EUR"
}
],
"has_more": false,
"next_cursor": null
}price_excl is de prijs die voor je account is afgesproken, anders de catalogusprijs. setup_fee_excl zijn de eenmalige kosten per nieuw abonnement (null als die er niet zijn).
Codes, namen en bedragen in de voorbeelden op deze pagina zijn verzonnen. De echte voor je account staan in het antwoord van GET /v1/products.
Het mobiele abonnement
Elk antwoord over een abonnement, en elke webhook, bevat dit object.
{
"object": "mobile_subscription",
"id": "ms_1234",
"environment": "live",
"status": "pending",
"product": { "code": "your_product_code", "name": "Mobiel Compleet" },
"user": { "first_name": "Jan", "last_name": "Jansen", "email": "[email protected]" },
"external_reference": "advisor-42",
"phone_number": null,
"number": { "type": "new", "porting": null },
"sim": { "type": "esim", "status": "to_order" },
"esim": { "ready": false, "activation_code": null, "confirmation_code": null },
"esim_swap": null,
"sip_channel": null,
"order": { "id": "ord_567", "number": "ORD20261187" },
"cancellation": null,
"created_at": "2026-11-03T14:02:11+01:00",
"activated_at": null
}- status
awaiting_approval(boven de daglimiet, wacht op akkoord) ·pending(wordt geleverd) ·needs_attention(vraagt handwerk en duurt langer, maar is niet geweigerd) ·active·blocked·ended·cancelled(bestelling geannuleerd vóór levering).- phone_
number - Het 06-nummer zodra het bekend is (event
subscription.number_assigned). - number
typeisnewofporting. Bij nummerbehoud staat inporting:current_number,status(requested,planned,completedofrejected) enplanned_date.- sim
typeisesim, ofphysicalbij een fysieke simkaart die al op je account stond.status:to_order·ordered·shipped·active·blocked·ended.- esim
readywordt waar zodra de eSIM klaarstaat.activation_codeis de LPA-code (LPA:1$…) enconfirmation_codede bevestigingscode; allebei alleen metesim:readen alleen in een antwoord, nooit in een webhook.- esim_
swap - Na een aangevraagde nieuwe eSIM:
status(ready,completed,revokedoffailed),requested_at,completed_aten, metesim:readen alleen bijGET …/{id}, de codes van de nieuwe eSIM. Nacompletedstaat de nieuwe eSIM inesim. - sip_
channel - Na een aanvraag:
status(pending,activeofremoved),username,registeredenregistered_at. De gebruikersnaam en de registratie alleen bijactive; het wachtwoord staat hier nooit in. - webrtc_
login - Bij een browserlogin:
username,status(active) enonline(zoals de gateway het meldt, ofnull). Andersnull. - cancellation
- Bij een opzegging:
ends_at(de laatste dag dat het abonnement werkt) enstops_on(de dag dat het stopt). - external_
reference - Je eigen kenmerk, bijvoorbeeld het id van de medewerker in je systeem. Komt terug in elk antwoord en elke webhook.
- order
- De bestelling waar het abonnement uit kwam, met
idennumber.
Bestellen
Bestel een nieuw abonnement met een nieuw nummer:
POST /v1/mobile-subscriptions
{
"product": "your_product_code",
"user": { "first_name": "Jan", "last_name": "Jansen", "email": "[email protected]" },
"external_reference": "advisor-42",
"number": { "type": "new" },
"sim": { "type": "esim" }
}Of neem een bestaand 06-nummer mee (nummerbehoud):
{
"product": "your_product_code",
"user": { "first_name": "Jan", "last_name": "Jansen" },
"number": {
"type": "porting",
"current_number": "+31612345678",
"provider": "Example Mobile",
"customer_number": "12345678",
"timing": "contract_end"
}
}- product
- Verplicht. Een code uit
GET /v1/products. - user.
first_ name, user. last_ name - Verplicht, 1–60 tekens. De naam komt op de factuurregel en in de verbruiksspecificatie.
- user.
email - Optioneel. Het adres waar de eSIM naartoe gaat; leeg is het hoofdadres van je account. Je systeem kan de eSIM ook zelf laten installeren met de activatiecode (
esim:read). - external_
reference - Optioneel, hooguit 100 tekens.
- number.
type new(standaard) ofporting. Bijportingiscurrent_numberverplicht (een Nederlands 06-nummer) enprovider(de huidige aanbieder). Optioneel zijncustomer_numberensim_numberbij die aanbieder, entiming:contract_end(standaard) ofasap. Metasapga je akkoord met eventuele afkoopkosten bij de huidige aanbieder.- sim.
type - Alleen
esimin deze versie.
- Antwoord: 202 met het abonnement, status
pendingofawaiting_approval. - VoiceOne stuurt bij een bestelling via de API geen orderbevestiging en geen oplevermail. Je systeem volgt de stand via webhooks.
- Een SIP-kanaal vraag je na het bestellen apart aan (zie SIP-kanaal).
Beheren
Gegevens wijzigen
PATCH /v1/mobile-subscriptions/{id}
{
"user": { "first_name": "Anna", "last_name": "de Vries", "email": "[email protected]" },
"external_reference": "advisor-57"
}Alleen user.first_name, user.last_name, user.email en external_reference kunnen veranderen. Stuur alleen wat verandert. Antwoord 200 met het abonnement.
Blokkeren en deblokkeren
POST /v1/mobile-subscriptions/{id}/block
POST /v1/mobile-subscriptions/{id}/unblock- Blokkeert bellen, sms en data meteen, bijvoorbeeld bij verlies of als iemand uit dienst gaat. Geen body. Het kan tot ongeveer 90 seconden duren; antwoord 200 met het abonnement.
- Al (niet) geblokkeerd, of geen actieve simkaart: 409
invalid_state. Loopt er al een actie: 409busy. Deblokkeren kan alleen als je zelf blokkeerde, niet bij een blokkade door VoiceOne.
Opzeggen
POST /v1/mobile-subscriptions/{id}/cancel
{ "reason": "Employee left the company" }- Optioneel een
reason(hooguit 500 tekens). Het abonnement stopt na een opzegtermijn van één maand, of aan het einde van een lopend contract als dat later is. Een mobiel abonnement stopt altijd per de 1e van een maand. - Antwoord 200 met het abonnement en
cancellation, bijvoorbeeld{ "ends_at": "2026-12-31", "stops_on": "2027-01-01" }. Alleen bijactiveofblocked. Een bestelling die nog geleverd wordt, zeg je op via [email protected]. - Al opgezegd: 409
invalid_state. Account geblokkeerd: 403account_blocked. VoiceOne mailt de gebruiker niet. Je krijgt de webhooksubscription.cancellation_scheduleden op de einddatumsubscription.ended.
Nieuwe eSIM
POST /v1/mobile-subscriptions/{id}/esim-swap
{
"user": { "first_name": "Anna", "last_name": "de Vries", "email": "[email protected]" }
}- Een nieuwe eSIM op hetzelfde nummer, bijvoorbeeld bij een nieuwe telefoon of als het nummer naar een andere medewerker gaat. Geef dan optioneel de nieuwe
usermee. De huidige eSIM blijft werken tot de nieuwe is geïnstalleerd. - Het kan tot ongeveer 90 seconden duren. Antwoord 200 met
esim_swap.status: "ready"en, metesim:read, de codes van de nieuwe eSIM. Werd het verzoek verstuurd maar niet bevestigd, dan 202 metesim_swap.status: "failed": probeer het dan niet opnieuw, VoiceOne kijkt het na. - Alleen voor een actieve, niet-geblokkeerde eSIM. Staat er al een nieuwe klaar: 409. Hooguit een paar keer per 24 uur per simkaart, daarboven 429. Webhooks:
subscription.esim_swap_readyen na de installatiesubscription.esim_swap_completed. - Gaat het nummer naar een andere medewerker? Haal dan in One de toegang van de vorige medewerker weg (Mijn account → Gebruikers), anders kan die de codes van de nieuwe eSIM inzien.
SIP-kanaal
Bij Mobiel Compleet kan elke gebruiker een SIP-kanaal krijgen, zonder extra kosten. Daarmee meld je een SIP-client aan voor die gebruiker, bijvoorbeeld de dialer of softphone in je eigen systeem.
Aanvragen
POST /v1/mobile-subscriptions/{id}/sip-channel, zonder body en met eenIdempotency-Key. Antwoord 202 met het abonnement ensip_channel.status: "pending".- VoiceOne maakt het kanaal binnen een paar minuten zodra de simkaart actief is. Dan volgt de webhook
subscription.sip_channel_ready. - Is er al een kanaal, of loopt de aanvraag nog, dan 200 met het abonnement zoals het is: er komt nooit een tweede kanaal.
- Geen Mobiel Compleet: 409
not_supported. Beëindigd: 409invalid_state. Account geblokkeerd: 403account_blocked. Hoort er nog een kanaal bij een vorige eigenaar van de simkaart: 409invalid_state. VoiceOne ruimt het op; vraag het daarna opnieuw aan.
Inloggegevens
{
"object": "sip_credentials",
"subscription_id": "ms_1234",
"environment": "live",
"server": "mv.voiceone.nl",
"port": null,
"username": "example7k2q",
"password": "…",
"extension": "301",
"registration": {
"registered": true,
"registered_at": "2026-11-03T10:00:00+01:00",
"checked_at": "2026-11-03T10:05:12+01:00"
},
"instructions": "Register a SIP client with this username and password on the server. Then dial 901 …"
}GET /v1/mobile-subscriptions/{id}/sip-credentials, met het rechtsip:read. Alleen alssip_channel.statusactiveis, anders 404 metdetails.sip_channel_status.- Het wachtwoord staat alleen in dit antwoord, nooit in lijsten of webhooks, en het antwoord heeft
Cache-Control: no-store. Bewaar het net zo zorgvuldig als je API-sleutel. - Heeft het abonnement een browserlogin, dan gebruikt onze gateway het kanaal: dan geeft dit endpoint 409
invalid_state. Eén kanaal kan niet tegelijk in de browser én in een eigen SIP-client. portisnullals VoiceOne geen vaste poort opgeeft: dan geldt de standaard van je SIP-client.extensionkannullzijn als die nog niet bekend is. De registratie (registered,registered_at) leest VoiceOne hooguit eens per tien minuten uit;checked_atzegt wanneer.
Aanmelden
- Registreer je SIP-client met
usernameenpasswordopserver. - Bel vanaf die client één keer 901, toets de extensie van de gebruiker en de pincode (standaard 0000). De gebruiker is dan aangemeld op dit kanaal.
- Staat het IP-adres van de client op de IP-whitelist van je account, dan meldt de gebruiker zich voortaan vanzelf aan.
Bellen in de browser
Met onze WebRTC-gateway belt een gebruiker vanuit de browser, bijvoorbeeld in je CRM, via het SIP-kanaal van zijn Mobiel Compleet-abonnement. Je hebt geen eigen SIP-server of softphone nodig: de browser praat WebRTC met de gateway, en de gateway meldt het kanaal aan bij ons SIP-platform.
- Bellen in de browser staat per account aan; vraag het aan via [email protected]. Tot die tijd geeft
POST …/webrtc-login403webrtc_disabled. - Het abonnement is Mobiel Compleet en heeft een actief SIP-kanaal (
POST …/sip-channel, daarna de webhooksubscription.sip_channel_ready). - Eén browserlogin per abonnement. Dat kanaal gebruik je dan niet ook nog in een eigen SIP-client.
Zo werkt het
POST /v1/mobile-subscriptions/{id}/webrtc-login, zonder body en met eenIdempotency-Key. Antwoord 201 met de login (object: "webrtc_login"), zonder wachtwoord. Bestaat er al een, dan 200 met die login.GET /v1/mobile-subscriptions/{id}/webrtc-loginmet het rechtsip:read: gebruikersnaam, wachtwoord,websocket_urlenice_servers(TURN met dezelfde login). Het antwoord heeftCache-Control: no-store.- Geef die gegevens via je eigen, ingelogde sessie aan de browser van de gebruiker en meld aan met een SIP-over-WebSocket-bibliotheek zoals JsSIP.
readyistruezodra de gateway de login kent (binnen ongeveer een minuut). - Nieuw wachtwoord:
POST …/webrtc-login/reset-password; het oude werkt binnen een minuut niet meer. Intrekken:POST …/webrtc-login/revoke; lopende gesprekken worden dan verbroken.
GET /v1/mobile-subscriptions/{id}/webrtc-login
{
"object": "webrtc_credentials",
"subscription_id": "ms_1234",
"environment": "live",
"username": "123456-k3x9qa",
"password": "…",
"sip_uri": "sip:[email protected]",
"websocket_url": "wss://webrtc.voiceone.nl/ws",
"ice_servers": [
{
"urls": ["turns:turn.voiceone.nl:443?transport=tcp", "turn:turn.voiceone.nl:3478"],
"username": "123456-k3x9qa",
"credential": "…"
}
],
"ready": true,
"limits": { "max_concurrent": 2, "max_calls_per_hour": 120, "max_call_seconds": 7200,
"allow_international": false, "allow_premium": false },
"online": true,
"online_at": "2026-11-03T14:05:03+01:00"
}Voorbeeld met JsSIP
// login = the response of GET /v1/mobile-subscriptions/{id}/webrtc-login,
// fetched by your server and handed to the signed-in user's browser.
const ua = new JsSIP.UA({
sockets: [new JsSIP.WebSocketInterface(login.websocket_url)],
uri: login.sip_uri,
password: login.password,
register: true,
session_timers: false,
});
const pcConfig = { iceServers: login.ice_servers };
ua.on('newRTCSession', (e) => {
if (e.originator === 'remote') {
e.session.answer({ mediaConstraints: { audio: true, video: false }, pcConfig });
}
});
ua.start();
// Call out:
const session = ua.call('0612345678', { mediaConstraints: { audio: true, video: false }, pcConfig });Het wachtwoord komt in de browser van de gebruiker: geef elke gebruiker zijn eigen login, bewaar het niet in localStorage, en trek de login in als iemand vertrekt.
Wat je kunt bellen
- Nederlandse nummers, 0800, 14-nummers, interne nummers (2–4 cijfers), 112 en 901 (aanmelden met extensie en pincode). Buitenland en betaalnummers (0900, 0906, 0909, 18xx) alleen als VoiceOne dat voor de login aanzet.
- Standaardlimieten per login: 2 gesprekken tegelijk, 120 uitgaande gesprekken per uur en 2 uur per gesprek. VoiceOne kan ze per login aanpassen; ze staan in
limits. - Inkomende gesprekken op het nummer gaan in de browser over zolang de gebruiker is aangemeld.
- Is het abonnement geblokkeerd of beëindigd, is het account geblokkeerd, of staat de API voor je account uit, dan werkt de login binnen een minuut niet meer.
Weigeringen (SIP-status)
- 401
- Onbekende login of fout wachtwoord bij het aanmelden.
- 403
- Dit nummer mag deze login niet bellen (buitenland, betaalnummer of een ander domein).
- 404
- Geen geldig nummer.
- 486
- Limiet van de login bereikt (tegelijk of per uur).
- 503
- De gateway is tijdelijk vol; probeer het zo opnieuw.
Webhooks
Stel in One per omgeving één of meer adressen in en kies de events (niets gekozen is alles). Elke wijziging vraagt tweestapsverificatie. Elk adres krijgt een eigen geheim (whsec_…), dat je in One kunt inzien en vernieuwen. Een melding ontstaat binnen ongeveer een minuut na de wijziging.
Het adres moet met https:// beginnen, een domeinnaam hebben (geen IP-adres), van buitenaf bereikbaar zijn en poort 443 of 8443 gebruiken.
{
"id": "evt_9c2f0a1b7d4e8c3a5f6b2d1e",
"object": "event",
"type": "subscription.active",
"environment": "live",
"created_at": "2026-11-03T14:05:40+01:00",
"data": { "object": { "object": "mobile_subscription", "id": "ms_1234", … } }
}Headers
- VoiceOne-Event-Id
- Het
idvan de melding. Ontdubbel hierop: een melding kan vaker binnenkomen, bijvoorbeeld na een time-out. - VoiceOne-Event-Type
- Het event, zoals
subscription.active. - VoiceOne-Signature
t=<unix-tijd>,v1=<hex>, metv1 = HMAC-SHA256(geheim, "<t>.<ruwe body>").
Handtekening controleren
Controleer elke melding vóór je hem verwerkt: bereken de HMAC over de ruwe body precies zoals die binnenkwam, vergelijk in constante tijd met v1 en wijs af als t meer dan vijf minuten afwijkt.
import crypto from 'node:crypto';
// rawBody: the request body exactly as received (a string, not re-serialised JSON).
export function verifyVoiceOne(rawBody, signatureHeader, secret) {
const parts = Object.fromEntries(signatureHeader.split(',').map((p) => p.split('=')));
const t = Number(parts.t);
if (!t || Math.abs(Date.now() / 1000 - t) > 300) return false;
const expected = crypto.createHmac('sha256', secret).update(t + '.' + rawBody).digest();
const given = Buffer.from(parts.v1 || '', 'hex');
return given.length === expected.length && crypto.timingSafeEqual(given, expected);
}<?php
function verifyVoiceOne(string $rawBody, string $signatureHeader, string $secret): bool {
parse_str(str_replace(',', '&', $signatureHeader), $parts);
$t = (int)($parts['t'] ?? 0);
if ($t === 0 || abs(time() - $t) > 300) return false;
$expected = hash_hmac('sha256', $t . '.' . $rawBody, $secret);
return hash_equals($expected, (string)($parts['v1'] ?? ''));
}
$raw = file_get_contents('php://input');
if (!verifyVoiceOne($raw, $_SERVER['HTTP_VOICEONE_SIGNATURE'] ?? '', (string)getenv('VOICEONE_WEBHOOK_SECRET'))) {
http_response_code(400);
exit;
}Aflevering
- Een 2xx binnen 10 seconden is gelukt. Doorverwijzingen worden niet gevolgd.
- Anders probeert VoiceOne het opnieuw na 1 minuut, 5 minuten, 30 minuten, 2 uur en 12 uur (zes pogingen). Daarna stopt het; in One zie je dat en kun je de melding opnieuw versturen.
- De volgorde is niet gegarandeerd. Gebruik
created_aten het object in de melding als de actuele stand. - Meldingen blijven 30 dagen bewaard. Met de testknop in One krijg je een melding van het type
test.ping.
Events
- subscription.
created - Nieuw abonnement aangenomen.
- subscription.
awaiting_ approval - Boven de daglimiet: wacht op akkoord van VoiceOne.
- subscription.
needs_ attention - Vraagt handwerk en duurt langer, maar is niet geweigerd.
- subscription.
active - Het abonnement werkt.
- subscription.
esim_ ready - De eSIM staat klaar. Haal de codes op met
GET …/{id}(metesim:read). - subscription.
number_ assigned - Het 06-nummer is bekend.
- subscription.
blocked - Geblokkeerd.
- subscription.
unblocked - De blokkade is opgeheven.
- subscription.
cancellation_ scheduled - Opgezegd.
cancellationnoemt de datums. - subscription.
ended - Het abonnement is gestopt.
- subscription.
cancelled - De bestelling is vóór levering geannuleerd.
- subscription.
esim_ swap_ ready - Een nieuwe eSIM staat klaar om te installeren.
- subscription.
esim_ swap_ completed - De nieuwe eSIM is geïnstalleerd.
- subscription.
sip_ channel_ ready - Het SIP-kanaal is klaar. Haal de gegevens op met
sip-credentials. - porting.
planned - Nummerbehoud is ingepland, of de datum is verzet (
number.porting.planned_date). - porting.
completed - Het nummer is overgezet.
- porting.
rejected - Nummerbehoud is afgewezen. VoiceOne zoekt uit wat er nodig is.
Sandbox
- Zelfde adres, endpoints en antwoorden, met een
vo_test_-sleutel.environmentis dan"sandbox". - Niets is echt: er ontstaan geen bestellingen, facturen of mails. Ids beginnen met
ms_test_, nummers met+316000(bel ze niet) en de activatiecode isLPA:1$sandbox.voiceone.nl$TEST-…. - Een nieuw abonnement is na ongeveer een minuut vanzelf
active. Een SIP-kanaal ook, ongeveer een minuut na de aanvraag, als het abonnement actief is. GET …/sip-credentialsgeeft in de sandbox voor elk abonnement hetzelfde echte testkanaal van VoiceOne (shared_test_channel: true), zodat je het aanmelden echt kunt proberen. Is dat nog niet ingesteld, dan 503unavailable.- Een browserlogin in de sandbox is nagebootst (
simulated: true,ready: false): hij werkt niet op de gateway. Echt bellen in de browser kan alleen live.
Gebeurtenissen nabootsen
Met POST /v1/sandbox/simulate (met Idempotency-Key) zet je een sandbox-abonnement in een stand en verstuur je de bijbehorende webhook. Mogelijke events:
subscription.activesubscription.needs_attentionsubscription.blockedsubscription.unblockedporting.plannedporting.completedsubscription.esim_swap_completedsubscription.endedsubscription.sip_channel_ready
POST /v1/sandbox/simulate
{ "event": "subscription.active", "subscription": "ms_test_8f2k1a" }subscription.esim_swap_completed werkt na een esim-swap, subscription.ended na een cancel en subscription.sip_channel_ready na een sip-channel op een actief abonnement.
Hulp
Vragen over de API, of werkt iets niet zoals hier staat? Mail [email protected] en noem de request_id uit het antwoord.
Ondersteuning voor de API en andere ontwikkelvragen gaat alleen per e-mail via dit adres, niet via de gewone klantenservice, de chat of de telefoon.
Storingen en onderhoud staan op voiceone.nl/status.
Open data zonder sleutel (prijzen, domeinnamen, storingen) vind je bij Voor ontwikkelaars.