Authentication
Choose one on the subscription:
- None. Rely on the signature (recommended) or your network controls.
- Bearer token. We send
Authorization: Bearer {your token}. - OAuth 2.0 client credentials (Microsoft Entra). Add an OAuth connection in the developer portal and choose it on the subscription. We request a token from your tenant and send it as a bearer token. If your endpoint answers
401, we get a fresh token and try once more straight away.
Any of these can be combined with a signing secret.
Checking the signature
When the subscription has a signing secret, each request carries:
X-Syncor-Signature: sha256={hex HMAC-SHA256 of "{X-Syncor-Timestamp}.{raw body}" using your signing secret}
To check it:
- Read the raw request body, exactly as received, before parsing it.
- Join the
X-Syncor-Timestampheader, a full stop and the raw body. - Compute the HMAC-SHA256 of that with your signing secret, as lowercase hex, and put
sha256=in front. - Compare it with
X-Syncor-Signatureusing a constant-time comparison. - Reject requests whose timestamp is more than 5 minutes from your clock. Every attempt, including retries, gets a fresh timestamp.
C# (.NET 5 or later):
using System.Linq;
using System.Security.Cryptography;
using System.Text;
static bool IsValidSyncorSignature(byte[] rawBody, string timestamp, string signature, string secret)
{
using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret));
var signed = Encoding.UTF8.GetBytes(timestamp + ".").Concat(rawBody).ToArray();
var expected = "sha256=" + Convert.ToHexString(hmac.ComputeHash(signed)).ToLowerInvariant();
return CryptographicOperations.FixedTimeEquals(
Encoding.UTF8.GetBytes(expected),
Encoding.UTF8.GetBytes(signature ?? ""));
}
Node.js:
import crypto from 'node:crypto';
function isValidSyncorSignature(rawBody, timestamp, signature, secret) {
const expected = 'sha256=' + crypto.createHmac('sha256', secret)
.update(`${timestamp}.`)
.update(rawBody) // a Buffer with the raw request body
.digest('hex');
const received = Buffer.from(signature ?? '');
return received.length === expected.length
&& crypto.timingSafeEqual(received, Buffer.from(expected));
}
PHP:
function isValidSyncorSignature(string $rawBody, string $timestamp, string $signature, string $secret): bool
{
$expected = 'sha256='.hash_hmac('sha256', $timestamp.'.'.$rawBody, $secret);
return hash_equals($expected, $signature);
}
Rotating the secret
Rotate secret on the subscription replaces the secret at once and shows the new one a single time. Deliveries after that are signed with the new secret, so update your receiver straight away. A delivery that fails your check in between is retried and succeeds once your receiver has the new secret.