Signed Request
In production environments, we recommend using signed requests for authenticating API calls rather than basic authentication. A signed request is hashed and encoded and includes a timestamp so it is more secure.
Application Signed Request
The following pseudocode example and table demonstrates and explains how to sign a request for the Sinch platform. The result of this should be included in the HTTP authorization
header sent with the HTTP request.
content-MD5 = Base64 ( MD5 ( UTF8 ( [BODY] ) ) )
Scheme = "application"
Signature = Base64 ( HMAC-SHA256 ( Base64-Decode ( ApplicationSecret ), UTF8 ( StringToSign ) ) );
StringToSign = HTTP-Verb + "\n" +
content-MD5 + "\n" +
content-type + "\n" +
CanonicalizedHeaders + "\n" +
CanonicalizedResource;
authorization = Scheme + " " + ApplicationKey + ":" + Signature
Pseudocode Component | Description |
---|---|
CanonicalizedHeaders |
The only required header is x-timestamp . |
CanonicalizedResource |
The path to the API resource. For example, calling/v1/callouts . |
ApplicationKey |
The key for your Voice application found on your dashboard. |
ApplicationSecret |
The secret for your Voice application found on your dashboard. Important!: The Application Secret value must be base64-decoded from before it's used for signing. |
Note:
HTTP headers are case-insensitive, so you don't need to worry about casing.
Example of an application signed request
For the following POST request to the protected resource /calling/v1/callouts,
POST /calling/v1/callouts
x-timestamp: 2014-06-04T13:41:58Z
content-type: application/json
{"message":"Hello world"}
the signature should be formed like this:
content-MD5 = Base64 ( MD5 ( UTF8 ( [BODY] ) ) )
jANzQ+rgAHyf1MWQFSwvYw==
StringToSign
POST
jANzQ+rgAHyf1MWQFSwvYw==
application/json
x-timestamp:2014-06-04T13:41:58Z
/calling/v1/callouts
Signature = Base64 ( HMAC-SHA256 ( Base64-Decode ( ApplicationSecret:"JViE5vDor0Sw3WllZka15Q==" ), UTF8 ( StringToSign ) ) )
qDXMwzfaxCRS849c/2R0hg0nphgdHciTo7OdM6MsdnM=
HTTP authorization Header authorization: application
5F5C418A0F914BBC8234A9BF5EDDAD97:qDXMwzfaxCRS849c/2R0hg0nphgdHciTo7OdM6MsdnM=
Note
For requests that don’t contain a body (like GET requests) or requests where the body is empty, the Content-MD5 value of StringToSign should be left empty, as in the following example:
StringToSign = HTTP-Verb + "\n" +
"\n" +
content-type + "\n" +
CanonicalizedHeaders + "\n" +
CanonicalizedResource;
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 specifications.
Important!
The timestamp must be in the Coordinated Universal Time (UTC) timezone.
Example
x-timestamp: 2014-06-02T15:39:31.2729234Z
Example implementations of application signing
Note:
Most of these examples require some setup steps before you can use them. Follow the steps in each language's getting started guide along with these code samples to see how to use a signed request for authentication.
const createHmac = require('create-hmac');
const crypto = require('crypto');
const hash = crypto.createHash('md5');
const utf8 = require('utf8');
const https = require('https');
const util = require('util');
///////////////////////////////////////////////////////////////////////////////////////////
// Add key,secret,cli and endpoint from your chosen application details from dashboard.sinch.com
//////////////////////////////////////////////////////////////////////////////////////////
const application = {
key: '', //key: The application key from the application you wish to use
secret: '', //secret: The secret of the application you wish to use
cli: '', //cli: A number you have purchased from us, or your registered number for test purposes
endpoint: '' //endpoint: The destination E164 formatted number that will receive the callout
};
const bodyData = JSON.stringify({
method: 'ttsCallout',
ttsCallout: {
cli: application.cli,
destination: {
type: 'number',
endpoint: application.endpoint
},
locale: 'en-US',
prompts: '#ssml[<speak><prosody rate="medium">This is a callout application signing test</prosody></speak>];'
}
});
let hmac = crypto.createHmac('sha256', Buffer.from(application.secret, 'base64'));
let contentMD5=hash.update(utf8.encode(bodyData)).digest('base64');
let contentLength = Buffer.byteLength(bodyData);
let timeStampISO = new Date().toISOString()
let stringToSign = 'POST' + '\n' +
contentMD5 + '\n' +
'application/json; charset=UTF-8' + '\n' +
'x-timestamp:'+ timeStampISO + '\n' +
'/calling/v1/callouts';
hmac.update(stringToSign);
let signature = hmac.digest('base64');
const options = {
method: 'POST',
hostname: 'calling.api.sinch.com',
port: 443,
path: '/calling/v1/callouts',
headers: {
'content-Type': 'application/json; charset=UTF-8',
'x-timestamp': timeStampISO ,
'content-length': contentLength,
'authorization': String('application '+ application.key +':' + signature)
},
data: bodyData
}
console.log(util.inspect(options, false, null, true))
const req = https.request(options, (res) => {
res.setEncoding('utf8');
res.on('data', (chunk) => {
console.log(`:: body response: => ${chunk}`);
});
res.on('end', () => {
console.log(':: end of data in body response.');
});
});
req.on('error', (e) => {
console.error(`problem with request: ${e.message}`);
});
req.write(bodyData);
req.end();
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Base64;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
public class App {
public static void main(String[] args) throws NoSuchAlgorithmException, InvalidKeyException, IOException, InterruptedException {
var applicationKey = "";
var applicationSecret = "";
var b64DecodedApplicationSecret = Base64.getDecoder().decode(applicationSecret);
var fromNumber = "";
var toNumber = "";
var sinchVoiceUrl = "https://calling.api.sinch.com/calling/v1/callouts";
var calloutRequest = """
{
"method": "ttsCallout",
"ttsCallout": {
"cli": "%s",
"destination": {
"type": "number",
"endpoint": "%s"
},
"locale": "en-US",
"text": "Hello, this is a call from Sinch. Congratulations! You made your first call."
}
}
""".formatted(fromNumber, toNumber);
byte[] encodedCalloutRequest = calloutRequest.getBytes(StandardCharsets.UTF_8);
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(encodedCalloutRequest);
var encodedMd5ToBase64CalloutRequest = Base64.getEncoder().encode(md.digest());
var httpVerb = "POST";
var requestContentType = "application/json; charset=UTF-8";
var timeNow = ZonedDateTime.now(ZoneOffset.UTC).format(DateTimeFormatter.ISO_INSTANT);
var requestTimeStamp = "x-timestamp:" + timeNow;
var requestUriPath = "/calling/v1/callouts";
var stringToSign = String.join(System.lineSeparator()
, httpVerb
, new String(encodedMd5ToBase64CalloutRequest, StandardCharsets.UTF_8)
, requestContentType
, requestTimeStamp
, requestUriPath
);
SecretKeySpec secretKeySpec = new SecretKeySpec(b64DecodedApplicationSecret, "HmacSHA256");
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(secretKeySpec);
byte[] hmacSha256 = mac.doFinal(stringToSign.getBytes());
var authorizationSiganture = new String(Base64.getEncoder().encode(hmacSha256), StandardCharsets.UTF_8);
var httpClient = HttpClient.newBuilder().build();
var request = HttpRequest.newBuilder()
.POST(HttpRequest.BodyPublishers.ofString(calloutRequest))
.uri(URI.create(sinchVoiceUrl))
.header("content-type", requestContentType)
.header("authorization", "application " + applicationKey + ":" + authorizationSiganture)
.header("x-timestamp", timeNow)
.build();
var response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
}
}
import hashlib
import hmac
import base64
from datetime import datetime, timezone
import requests
import json
application_key = ''
application_secret = ''
from_number = ''
to_number = ''
callout_request = {
"method": "ttsCallout",
"ttsCallout": {
"cli": from_number,
"destination": {
"type": "number",
"endpoint": to_number
},
"locale": "en-US",
"text": "Hello, this is a call from Sinch. Congratulations! You made your first call."
}
}
b64_decoded_application_secret = base64.b64decode(application_secret)
encoded_callout_request = json.dumps(callout_request).encode()
md5_callout_requst = hashlib.md5(encoded_callout_request)
encoded_MD5_to_base64_callout_request = base64.b64encode(md5_callout_requst.digest())
http_verb = 'POST'
request_content_type = 'application/json; charset=UTF-8'
time_now = datetime.now(timezone.utc).isoformat()
request_timestamp = "x-timestamp:" + time_now
request_uri_path = '/calling/v1/callouts'
string_to_sign = (http_verb + "\n"
+ encoded_MD5_to_base64_callout_request.decode() + "\n"
+ request_content_type + "\n"
+ request_timestamp + "\n"
+ request_uri_path)
authorization_signature = base64.b64encode(
hmac.new(b64_decoded_application_secret, string_to_sign.encode(), hashlib.sha256).digest()
).decode()
headers = {
"content-type": request_content_type,
"authorization": (f"application {application_key}:{authorization_signature}"),
"x-timestamp": time_now
}
callout_response = requests.post(
"https://calling.api.sinch.com/calling/v1/callouts",
json=callout_request,
headers=headers
)
print(callout_response.json())
<?php
declare(strict_types=1);
$applicationKey = "";
$applicationSecret= "";
$b64DecodedApplicationSecret = base64_decode($applicationSecret, true);
$toNumber = "";
$fromNumber = "";
$sinchVoiceUrl = "https://calling.api.sinch.com/calling/v1/callouts";
$calloutRequest = [
"method" => "ttsCallout",
"ttsCallout" => [
"cli" => $fromNumber,
"destination" => [
"type" => "number",
"endpoint" => $toNumber
],
"locale" => "en-US",
"text" => "Hello, this is a call from Sinch. Congratulations! You made your first call."
]
];
$encodedCalloutRequest = utf8_encode(json_encode($calloutRequest, JSON_UNESCAPED_UNICODE));
$md5CalloutRequest = md5($encodedCalloutRequest, true);
$encodedMd5ToBase64CalloutRequest = base64_encode($md5CalloutRequest);
$httpVerb = 'POST';
$requestContentType = 'application/json; charset=UTF-8';
date_default_timezone_set('UTC');
$timeNow = date(DateTime::ATOM);
$requestTimeStamp = "x-timestamp:" . $timeNow;
$requestUriPath = "/calling/v1/callouts/";
$stringToSign = $httpVerb . "\n"
. $encodedMd5ToBase64CalloutRequest . "\n"
. $requestContentType . "\n"
. $requestTimeStamp . "\n"
. $requestUriPath;
$authorizationSignature = base64_encode(hash_hmac("sha256", $stringToSign, $b64DecodedApplicationSecret, true));
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_HTTPHEADER => [
"content-type: {$requestContentType}",
"x-timestamp: {$timeNow}",
"authorization: application {$applicationKey}:{$authorizationSignature}"
],
CURLOPT_POSTFIELDS => json_encode($calloutRequest),
CURLOPT_URL => $sinchVoiceUrl,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => $httpVerb,
]);
$response = curl_exec($curl);
$error = curl_error($curl);
curl_close($curl);
echo $response;