Webhooks¶
Callbacks are a mechanism to let your monitoring and alerting systems know if and when a Sipfront test has succeeded or failed.
Securing your Callbacks¶
Callback requests from Sipfront to your endpoints are signed with a shared key, and the signature is provided in the Sipfront-Signature
header. This allows you to verify that the request is indeed coming from Sipfront.
An example of a Sipfront-Signature
header is:
Sipfront-Signature: t=1726872266,v1=de7c0dc1e62c08abf36f5bfb2bd2d0e34624a3f959f9d4ecaf13fc9823f495fb
The header consists of two parts:
t
: a timestamp in seconds since the Unix epochv1
: a SHA-256 HMAC of the timestamp and the shared key
You can verify the signature in various programming languages, for example in Perl:
use Digest::SHA qw/hmac_sha256_hex/;
...
sub _validate_signature {
my ($key, $sig, $payload) = @_;
die "Missing signature header\n" unless (defined $sig && length $sig);
my ($t, $v1, $v0) = split /,/, $sig;
die "Missing t value in signature header\n" unless ($t =~ s/^t=//);
die "Missing v1 value in signature header\n" unless ($v1 =~ s/^v1=//);
my $signed_payload = "$t.$payload";
my $digest = hmac_sha256_hex($signed_payload, $key);
die "Invalid signature value\n" unless($digest eq $v1);
if (abs(time - int($t)) > 300) {
die "Invalid time difference in signature\n";
}
return 1;
}
eval {
...
_validate_signature($shared_key, $header{'Sipfront-Signature'}, $request_body);
...
}
if ($@) {
# signature is invalid
} else
{
# signature is valid
}
In Python, it works as follows:
import hmac
import hashlib
import time
def _validate_signature(key, sig, payload):
# Check if the signature header is present
if not sig or not len(sig):
raise Exception("Missing signature header")
# Split the signature header into parts
parts = sig.split(',')
if len(parts) < 2:
raise Exception("Invalid signature header format")
t_part = parts[0]
v1_part = parts[1]
# Extract and validate the timestamp 't' value
if not t_part.startswith('t='):
raise Exception("Missing t value in signature header")
t = t_part[2:] # Remove 't=' prefix
# Extract and validate the signature 'v1' value
if not v1_part.startswith('v1='):
raise Exception("Missing v1 value in signature header")
v1 = v1_part[3:] # Remove 'v1=' prefix
# Construct the signed payload
signed_payload = f"{t}.{payload}"
# Compute the HMAC-SHA256 digest
digest = hmac.new(
key.encode('utf-8'),
signed_payload.encode('utf-8'),
hashlib.sha256
).hexdigest()
# Validate the signature value
if digest != v1:
raise Exception("Invalid signature value")
# Check the time difference to prevent replay attacks
current_time = int(time.time())
if abs(current_time - int(t)) > 300:
raise Exception("Invalid time difference in signature")
return True
# Example usage
try:
# Assume 'shared_key', 'header', and 'request_body' are defined elsewhere
shared_key = "your_shared_key"
header = {
'Sipfront-Signature': 't=1638300000,v1=abcd1234efgh5678ijkl9012mnop3456qrst7890uvwx'
}
request_body = '{"key": "value"}'
_validate_signature(shared_key, header['Sipfront-Signature'], request_body)
except Exception as e:
# Signature is invalid
print(f"Signature validation failed: {e}")
else:
# Signature is valid
print("Signature is valid.")