# Webhook Request Signing

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](https://dashboard.sinch.com/voice-v2/services). The signature is included in the `authorization` header of the request.

```shell
    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`](#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. |


## Timestamp

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](http://en.wikipedia.org/wiki/ISO_8601) specifications.

Important!
The timestamp must be in the Coordinated Universal Time (UTC) timezone.

**Example**

```shell Example of timestamp header
x-timestamp: 2026-06-02T15:39:31.2729234Z
```

## Webhook Signature Validation Example

In this example, assume that the *Webhook URL* is configured as `"https://yourdomain.com/sinch/webhook"`

```shell
    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=
```

Important!
The service Secret value must be base64-decoded from before it's used for signing/validation.

Note:
HTTP headers are case-insensitive, so you don't need to worry about casing.

## Webhook Request Validation

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.