The Sinch Platform can initiate webhook requests to an URL you define (Webhook URL) on events like call.incoming, call.answered, call.hangup and more. All Webhook requests are signed using your Service access key and secret pair found on your dashboard. The signature is included in the authorization header of the request.
authorization = "service" + " " + serviceKey + ":" + Signature
Signature = Base64 ( HMAC-SHA256 ( Base64-Decode( serviceSecret ), UTF8 ( StringToSign ) ) );
StringToSign = HTTP-Verb + "\n" +
Content-MD5 + "\n" +
content-type + "\n" +
CanonicalizedHeaders + "\n" +
CanonicalizedResource;
Content-MD5 = Base64 ( MD5 ( [BODY] ) )| Pseudocode Component | Description |
|---|---|
CanonicalizedHeaders | The only required header is x-timestamp. |
CanonicalizedResource | The path to the API resource. For example, /v2/projects/{projectId}/calls. |
Service ID/Service Key | The Id/Key for your Programmable Voice Service can be found on your dashboard, under Configure call behaviour. |
Service Secret | The secret for your Programmable Voice Service can also be found on your dashboard. |
The client must send a custom header x-timestamp (time) with each request that's validated by the server. This custom header is used to determine that the request is not too old. The timestamp is also part of the signature. The timestamp must be formatted to ISO 8061 specifications.
The timestamp must be in the Coordinated Universal Time (UTC) timezone.
Example
x-timestamp: 2026-06-02T15:39:31.2729234ZIn this example, assume that the Webhook URL is configured as "https://yourdomain.com/sinch/webhook"
serviceKey = 669E367E-6BBA-48AB-AF15-266871C28135
serviceSecret = BeIukql3pTKJ8RGL5zo0DA==
Headers:
{
host: 'yourdomain.com',
'content-length': '724',
authorization: 'service XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
baggage: 'SessionId+=+01KTSPWJP2YPH9KSAKC2SRP7W8',
'ce-id': '55f0102b-739a-4151-8caf-9b3aaf249859',
'ce-source': 'projects/727ce3a0-b9ac-4b65-8a53-29e4e0d5c270/services/b7430a33-38c8-4fba-9fb7-c66eae501d11',
'ce-specversion': '1.0',
'ce-time': '2026-06-10T21:27:04.0783345+00:00',
'ce-type': 'com.sinch.voice.webhook.v2',
'content-type': 'application/json; charset=utf-8',
traceparent: '00-5a8c4644667b4b60784f22c0544d335d-701eb65af934e502-01',
'x-forwarded-for': '15.228.118.128',
'x-forwarded-host': 'yourdomain.com',
'x-forwarded-proto': 'https',
'x-timestamp': '2026-06-10T21:27:04.1466768Z',
'accept-encoding': 'gzip'
}
Body:
{
event: 'call.webhook.call.answered',
call: {
callId: '01KTSPWYAFT6MZ3TM7H43K82VQ',
serviceId: 'b7430a33-38c8-4fba-9fb7-c66eae501d11',
projectId: '727ce3a0-b9ac-4b65-8a53-29e4e0d5c270',
sessionId: '01KTSPWHMJGTPV36FSBY7C6Z44',
to: {
type: 'STREAM', stream: [Object]
},
direction: 'OUTBOUND',
callResult: 'IN_PROGRESS',
callType: 'STREAM',
originationType: 'SERVER',
startTime: '2026-06-10T21:27:02.6695935',
answerTime: '2026-06-10T21:27:03.865752',
updateTime: '2026-06-10T21:27:03.865752',
callRate: {
currencyCode: 'EUR',
amount: 0.0028
},
callResourceUrl: '/v2/projects/727ce3a0-b9ac-4b65-8a53-29e4e0d5c270/calls/01KTSPWYAFT6MZ3TM7H43K82VQ'
}
}
Content-MD5 = Base64 ( MD5 ( [BODY] ) )
REWF+X220L4/Gw1spXOU7g==
StringToSign
POST
REWF+X220L4/Gw1spXOU7g==
application/json
x-timestamp:2014-09-24T10:59:41Z
/sinch/webhook
Signature = Base64 ( HMAC-SHA256 ( Base64-Decode( serviceSecret ), UTF8 ( StringToSign ) ) )
Tg6fMyo8mj9pYfWQ9ssbx3Tc1BNC87IEygAfLbJqZb4=
HTTP Authorization Header
authorization: service 669E367E-6BBA-48AB-AF15-266871C28135:Tg6fMyo8mj9pYfWQ9ssbx3Tc1BNC87IEygAfLbJqZb4=The service Secret value must be base64-decoded from before it's used for signing/validation.
HTTP headers are case-insensitive, so you don't need to worry about casing.
Your development platform that receives the Webhooks can verify that the request originated from Sinch by calculating the signature as described above and compare the result with the value contained in the service HTTP header.