curl --request POST \
--url https://api.spenza.com/api/v1.1/webhooks \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"operator": "SpenzaJ",
"network": "T-Mobile",
"authentication": {
"username": "partner-webhook",
"password": "a-secret-you-choose",
"token": "a-secret-you-choose",
"apiKey": "a-secret-you-choose",
"apiKeyHeader": "X-API-Key"
},
"webhookUrls": {
"messageUrl": "https://partner.example.com/hooks/sms",
"callbackMessageUrl": "https://partner.example.com/hooks/sms-status",
"voiceUrl": "https://partner.example.com/hooks/voice",
"callbackVoiceUrl": "https://partner.example.com/hooks/voice-status",
"voiceStreamUrl": "wss://partner.example.com/hooks/voice-stream"
},
"retryPolicy": {
"maxAttempts": 3,
"backoffStrategy": "exponential",
"initialDelaySeconds": 5,
"maxDelaySeconds": 300
},
"status": "active",
"description": "Primary SMS webhook for T-Mobile",
"update": false
}
'import requests
url = "https://api.spenza.com/api/v1.1/webhooks"
payload = {
"operator": "SpenzaJ",
"network": "T-Mobile",
"authentication": {
"username": "partner-webhook",
"password": "a-secret-you-choose",
"token": "a-secret-you-choose",
"apiKey": "a-secret-you-choose",
"apiKeyHeader": "X-API-Key"
},
"webhookUrls": {
"messageUrl": "https://partner.example.com/hooks/sms",
"callbackMessageUrl": "https://partner.example.com/hooks/sms-status",
"voiceUrl": "https://partner.example.com/hooks/voice",
"callbackVoiceUrl": "https://partner.example.com/hooks/voice-status",
"voiceStreamUrl": "wss://partner.example.com/hooks/voice-stream"
},
"retryPolicy": {
"maxAttempts": 3,
"backoffStrategy": "exponential",
"initialDelaySeconds": 5,
"maxDelaySeconds": 300
},
"status": "active",
"description": "Primary SMS webhook for T-Mobile",
"update": False
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
operator: 'SpenzaJ',
network: 'T-Mobile',
authentication: {
username: 'partner-webhook',
password: 'a-secret-you-choose',
token: 'a-secret-you-choose',
apiKey: 'a-secret-you-choose',
apiKeyHeader: 'X-API-Key'
},
webhookUrls: {
messageUrl: 'https://partner.example.com/hooks/sms',
callbackMessageUrl: 'https://partner.example.com/hooks/sms-status',
voiceUrl: 'https://partner.example.com/hooks/voice',
callbackVoiceUrl: 'https://partner.example.com/hooks/voice-status',
voiceStreamUrl: 'wss://partner.example.com/hooks/voice-stream'
},
retryPolicy: {
maxAttempts: 3,
backoffStrategy: 'exponential',
initialDelaySeconds: 5,
maxDelaySeconds: 300
},
status: 'active',
description: 'Primary SMS webhook for T-Mobile',
update: false
})
};
fetch('https://api.spenza.com/api/v1.1/webhooks', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.spenza.com/api/v1.1/webhooks",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'operator' => 'SpenzaJ',
'network' => 'T-Mobile',
'authentication' => [
'username' => 'partner-webhook',
'password' => 'a-secret-you-choose',
'token' => 'a-secret-you-choose',
'apiKey' => 'a-secret-you-choose',
'apiKeyHeader' => 'X-API-Key'
],
'webhookUrls' => [
'messageUrl' => 'https://partner.example.com/hooks/sms',
'callbackMessageUrl' => 'https://partner.example.com/hooks/sms-status',
'voiceUrl' => 'https://partner.example.com/hooks/voice',
'callbackVoiceUrl' => 'https://partner.example.com/hooks/voice-status',
'voiceStreamUrl' => 'wss://partner.example.com/hooks/voice-stream'
],
'retryPolicy' => [
'maxAttempts' => 3,
'backoffStrategy' => 'exponential',
'initialDelaySeconds' => 5,
'maxDelaySeconds' => 300
],
'status' => 'active',
'description' => 'Primary SMS webhook for T-Mobile',
'update' => false
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.spenza.com/api/v1.1/webhooks"
payload := strings.NewReader("{\n \"operator\": \"SpenzaJ\",\n \"network\": \"T-Mobile\",\n \"authentication\": {\n \"username\": \"partner-webhook\",\n \"password\": \"a-secret-you-choose\",\n \"token\": \"a-secret-you-choose\",\n \"apiKey\": \"a-secret-you-choose\",\n \"apiKeyHeader\": \"X-API-Key\"\n },\n \"webhookUrls\": {\n \"messageUrl\": \"https://partner.example.com/hooks/sms\",\n \"callbackMessageUrl\": \"https://partner.example.com/hooks/sms-status\",\n \"voiceUrl\": \"https://partner.example.com/hooks/voice\",\n \"callbackVoiceUrl\": \"https://partner.example.com/hooks/voice-status\",\n \"voiceStreamUrl\": \"wss://partner.example.com/hooks/voice-stream\"\n },\n \"retryPolicy\": {\n \"maxAttempts\": 3,\n \"backoffStrategy\": \"exponential\",\n \"initialDelaySeconds\": 5,\n \"maxDelaySeconds\": 300\n },\n \"status\": \"active\",\n \"description\": \"Primary SMS webhook for T-Mobile\",\n \"update\": false\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.spenza.com/api/v1.1/webhooks")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"operator\": \"SpenzaJ\",\n \"network\": \"T-Mobile\",\n \"authentication\": {\n \"username\": \"partner-webhook\",\n \"password\": \"a-secret-you-choose\",\n \"token\": \"a-secret-you-choose\",\n \"apiKey\": \"a-secret-you-choose\",\n \"apiKeyHeader\": \"X-API-Key\"\n },\n \"webhookUrls\": {\n \"messageUrl\": \"https://partner.example.com/hooks/sms\",\n \"callbackMessageUrl\": \"https://partner.example.com/hooks/sms-status\",\n \"voiceUrl\": \"https://partner.example.com/hooks/voice\",\n \"callbackVoiceUrl\": \"https://partner.example.com/hooks/voice-status\",\n \"voiceStreamUrl\": \"wss://partner.example.com/hooks/voice-stream\"\n },\n \"retryPolicy\": {\n \"maxAttempts\": 3,\n \"backoffStrategy\": \"exponential\",\n \"initialDelaySeconds\": 5,\n \"maxDelaySeconds\": 300\n },\n \"status\": \"active\",\n \"description\": \"Primary SMS webhook for T-Mobile\",\n \"update\": false\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.spenza.com/api/v1.1/webhooks")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"operator\": \"SpenzaJ\",\n \"network\": \"T-Mobile\",\n \"authentication\": {\n \"username\": \"partner-webhook\",\n \"password\": \"a-secret-you-choose\",\n \"token\": \"a-secret-you-choose\",\n \"apiKey\": \"a-secret-you-choose\",\n \"apiKeyHeader\": \"X-API-Key\"\n },\n \"webhookUrls\": {\n \"messageUrl\": \"https://partner.example.com/hooks/sms\",\n \"callbackMessageUrl\": \"https://partner.example.com/hooks/sms-status\",\n \"voiceUrl\": \"https://partner.example.com/hooks/voice\",\n \"callbackVoiceUrl\": \"https://partner.example.com/hooks/voice-status\",\n \"voiceStreamUrl\": \"wss://partner.example.com/hooks/voice-stream\"\n },\n \"retryPolicy\": {\n \"maxAttempts\": 3,\n \"backoffStrategy\": \"exponential\",\n \"initialDelaySeconds\": 5,\n \"maxDelaySeconds\": 300\n },\n \"status\": \"active\",\n \"description\": \"Primary SMS webhook for T-Mobile\",\n \"update\": false\n}"
response = http.request(request)
puts response.read_body{
"success": true,
"data": {
"id": "wh_69146d70ed68",
"operator": "SpenzaJ",
"network": "T-Mobile",
"eventType": "sms",
"status": "active",
"authentication": {
"type": "bearer",
"token": "***ENCRYPTED***"
},
"webhookUrls": {
"messageUrl": "https://partner.example.com/hooks/sms",
"callbackMessageUrl": null,
"voiceUrl": null,
"callbackVoiceUrl": null,
"voiceStreamUrl": null
},
"retryPolicy": {
"maxAttempts": 5,
"backoffStrategy": "exponential",
"initialDelaySeconds": 5,
"maxDelaySeconds": 300
},
"description": null,
"createdAt": "2026-07-10T10:00:00.000Z",
"updatedAt": "2026-07-10T10:00:00.000Z",
"signingSecret": "whsec_9b1f8c2e7a4d6053b8e1f2a9c4d7e6053b8e1f2a9c4d7e6053b8e1f2a9c4d70"
}
}Register or update a webhook
Register a webhook for operator SMS/voice events. At most one
registration per (account, operator, network) — eventType is not
part of that key, so registering a second event type for the same
operator+network replaces the first. Registering again for an existing
combination without update: true is a 409.
eventType: voice (or both) registers successfully as long as the
operator/network combination itself is valid — there is no
registration-time check that voice is actually enabled, unlike sms.
Voice events only fire for a line whose active plan includes voice
support; a line on a plan with no voice allowance won’t produce voice
events even with a registered webhook. This applies to voiceStreamUrl
too — there’s no separate “voice streaming” plan capability, it’s
gated by the same voice allowance as voiceUrl/callbackVoiceUrl.
voiceStreamUrl is delivered differently from every other field here:
instead of an HTTP POST, Spenza opens an outbound WebSocket connection
to it (accepts ws://, wss://, http://, or https://) whenever a
live voice call starts on that operator/network, sends a JSON
handshake frame, then streams binary PCM audio (8kHz, 16-bit, mono) in
real time for the duration of the call. It’s never used as a
POST .../webhooks/{id}/test target (see that operation).
signingSecret is returned only from this creation path (format
whsec_<64 hex chars>) — no read endpoint ever returns it again. Store
it immediately; losing it means re-registering. Use it to verify the
X-Spenza-Signature header on every delivery (see the Webhooks guide).
curl --request POST \
--url https://api.spenza.com/api/v1.1/webhooks \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"operator": "SpenzaJ",
"network": "T-Mobile",
"authentication": {
"username": "partner-webhook",
"password": "a-secret-you-choose",
"token": "a-secret-you-choose",
"apiKey": "a-secret-you-choose",
"apiKeyHeader": "X-API-Key"
},
"webhookUrls": {
"messageUrl": "https://partner.example.com/hooks/sms",
"callbackMessageUrl": "https://partner.example.com/hooks/sms-status",
"voiceUrl": "https://partner.example.com/hooks/voice",
"callbackVoiceUrl": "https://partner.example.com/hooks/voice-status",
"voiceStreamUrl": "wss://partner.example.com/hooks/voice-stream"
},
"retryPolicy": {
"maxAttempts": 3,
"backoffStrategy": "exponential",
"initialDelaySeconds": 5,
"maxDelaySeconds": 300
},
"status": "active",
"description": "Primary SMS webhook for T-Mobile",
"update": false
}
'import requests
url = "https://api.spenza.com/api/v1.1/webhooks"
payload = {
"operator": "SpenzaJ",
"network": "T-Mobile",
"authentication": {
"username": "partner-webhook",
"password": "a-secret-you-choose",
"token": "a-secret-you-choose",
"apiKey": "a-secret-you-choose",
"apiKeyHeader": "X-API-Key"
},
"webhookUrls": {
"messageUrl": "https://partner.example.com/hooks/sms",
"callbackMessageUrl": "https://partner.example.com/hooks/sms-status",
"voiceUrl": "https://partner.example.com/hooks/voice",
"callbackVoiceUrl": "https://partner.example.com/hooks/voice-status",
"voiceStreamUrl": "wss://partner.example.com/hooks/voice-stream"
},
"retryPolicy": {
"maxAttempts": 3,
"backoffStrategy": "exponential",
"initialDelaySeconds": 5,
"maxDelaySeconds": 300
},
"status": "active",
"description": "Primary SMS webhook for T-Mobile",
"update": False
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
operator: 'SpenzaJ',
network: 'T-Mobile',
authentication: {
username: 'partner-webhook',
password: 'a-secret-you-choose',
token: 'a-secret-you-choose',
apiKey: 'a-secret-you-choose',
apiKeyHeader: 'X-API-Key'
},
webhookUrls: {
messageUrl: 'https://partner.example.com/hooks/sms',
callbackMessageUrl: 'https://partner.example.com/hooks/sms-status',
voiceUrl: 'https://partner.example.com/hooks/voice',
callbackVoiceUrl: 'https://partner.example.com/hooks/voice-status',
voiceStreamUrl: 'wss://partner.example.com/hooks/voice-stream'
},
retryPolicy: {
maxAttempts: 3,
backoffStrategy: 'exponential',
initialDelaySeconds: 5,
maxDelaySeconds: 300
},
status: 'active',
description: 'Primary SMS webhook for T-Mobile',
update: false
})
};
fetch('https://api.spenza.com/api/v1.1/webhooks', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.spenza.com/api/v1.1/webhooks",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'operator' => 'SpenzaJ',
'network' => 'T-Mobile',
'authentication' => [
'username' => 'partner-webhook',
'password' => 'a-secret-you-choose',
'token' => 'a-secret-you-choose',
'apiKey' => 'a-secret-you-choose',
'apiKeyHeader' => 'X-API-Key'
],
'webhookUrls' => [
'messageUrl' => 'https://partner.example.com/hooks/sms',
'callbackMessageUrl' => 'https://partner.example.com/hooks/sms-status',
'voiceUrl' => 'https://partner.example.com/hooks/voice',
'callbackVoiceUrl' => 'https://partner.example.com/hooks/voice-status',
'voiceStreamUrl' => 'wss://partner.example.com/hooks/voice-stream'
],
'retryPolicy' => [
'maxAttempts' => 3,
'backoffStrategy' => 'exponential',
'initialDelaySeconds' => 5,
'maxDelaySeconds' => 300
],
'status' => 'active',
'description' => 'Primary SMS webhook for T-Mobile',
'update' => false
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.spenza.com/api/v1.1/webhooks"
payload := strings.NewReader("{\n \"operator\": \"SpenzaJ\",\n \"network\": \"T-Mobile\",\n \"authentication\": {\n \"username\": \"partner-webhook\",\n \"password\": \"a-secret-you-choose\",\n \"token\": \"a-secret-you-choose\",\n \"apiKey\": \"a-secret-you-choose\",\n \"apiKeyHeader\": \"X-API-Key\"\n },\n \"webhookUrls\": {\n \"messageUrl\": \"https://partner.example.com/hooks/sms\",\n \"callbackMessageUrl\": \"https://partner.example.com/hooks/sms-status\",\n \"voiceUrl\": \"https://partner.example.com/hooks/voice\",\n \"callbackVoiceUrl\": \"https://partner.example.com/hooks/voice-status\",\n \"voiceStreamUrl\": \"wss://partner.example.com/hooks/voice-stream\"\n },\n \"retryPolicy\": {\n \"maxAttempts\": 3,\n \"backoffStrategy\": \"exponential\",\n \"initialDelaySeconds\": 5,\n \"maxDelaySeconds\": 300\n },\n \"status\": \"active\",\n \"description\": \"Primary SMS webhook for T-Mobile\",\n \"update\": false\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.spenza.com/api/v1.1/webhooks")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"operator\": \"SpenzaJ\",\n \"network\": \"T-Mobile\",\n \"authentication\": {\n \"username\": \"partner-webhook\",\n \"password\": \"a-secret-you-choose\",\n \"token\": \"a-secret-you-choose\",\n \"apiKey\": \"a-secret-you-choose\",\n \"apiKeyHeader\": \"X-API-Key\"\n },\n \"webhookUrls\": {\n \"messageUrl\": \"https://partner.example.com/hooks/sms\",\n \"callbackMessageUrl\": \"https://partner.example.com/hooks/sms-status\",\n \"voiceUrl\": \"https://partner.example.com/hooks/voice\",\n \"callbackVoiceUrl\": \"https://partner.example.com/hooks/voice-status\",\n \"voiceStreamUrl\": \"wss://partner.example.com/hooks/voice-stream\"\n },\n \"retryPolicy\": {\n \"maxAttempts\": 3,\n \"backoffStrategy\": \"exponential\",\n \"initialDelaySeconds\": 5,\n \"maxDelaySeconds\": 300\n },\n \"status\": \"active\",\n \"description\": \"Primary SMS webhook for T-Mobile\",\n \"update\": false\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.spenza.com/api/v1.1/webhooks")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"operator\": \"SpenzaJ\",\n \"network\": \"T-Mobile\",\n \"authentication\": {\n \"username\": \"partner-webhook\",\n \"password\": \"a-secret-you-choose\",\n \"token\": \"a-secret-you-choose\",\n \"apiKey\": \"a-secret-you-choose\",\n \"apiKeyHeader\": \"X-API-Key\"\n },\n \"webhookUrls\": {\n \"messageUrl\": \"https://partner.example.com/hooks/sms\",\n \"callbackMessageUrl\": \"https://partner.example.com/hooks/sms-status\",\n \"voiceUrl\": \"https://partner.example.com/hooks/voice\",\n \"callbackVoiceUrl\": \"https://partner.example.com/hooks/voice-status\",\n \"voiceStreamUrl\": \"wss://partner.example.com/hooks/voice-stream\"\n },\n \"retryPolicy\": {\n \"maxAttempts\": 3,\n \"backoffStrategy\": \"exponential\",\n \"initialDelaySeconds\": 5,\n \"maxDelaySeconds\": 300\n },\n \"status\": \"active\",\n \"description\": \"Primary SMS webhook for T-Mobile\",\n \"update\": false\n}"
response = http.request(request)
puts response.read_body{
"success": true,
"data": {
"id": "wh_69146d70ed68",
"operator": "SpenzaJ",
"network": "T-Mobile",
"eventType": "sms",
"status": "active",
"authentication": {
"type": "bearer",
"token": "***ENCRYPTED***"
},
"webhookUrls": {
"messageUrl": "https://partner.example.com/hooks/sms",
"callbackMessageUrl": null,
"voiceUrl": null,
"callbackVoiceUrl": null,
"voiceStreamUrl": null
},
"retryPolicy": {
"maxAttempts": 5,
"backoffStrategy": "exponential",
"initialDelaySeconds": 5,
"maxDelaySeconds": 300
},
"description": null,
"createdAt": "2026-07-10T10:00:00.000Z",
"updatedAt": "2026-07-10T10:00:00.000Z",
"signingSecret": "whsec_9b1f8c2e7a4d6053b8e1f2a9c4d7e6053b8e1f2a9c4d7e6053b8e1f2a9c4d70"
}
}Authorizations
Bearer token obtained from POST /api/v1.1/auth/token.
Body
"SpenzaJ"
"T-Mobile"
sms, voice, both Credential your endpoint requires — Spenza presents it when calling you. This is not a payload-signing scheme (see signingSecret/X-Spenza-Signature for that).
Show child attributes
Show child attributes
Show child attributes
Show child attributes
Show child attributes
Show child attributes
active, inactive "Primary SMS webhook for T-Mobile"
Set true to overwrite an existing registration for this operator+network. Coerced via class-transformer — sending the string "false" is truthy and also overwrites; send a real JSON boolean.
Response
Registered (or updated, if update:true).

