Skip to content

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:

  1. t: a timestamp in seconds since the Unix epoch
  2. v1: 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.")