Security
Webhook requests are signed using a shared secret. You can find this in the webooks dashboard. The resulting signature with signing timestamp is sent with the request in Tickettailor-Webhook-Signature
header. We recommend to verify that the request is sent from Ticket Tailor. To do this, you must generate a HMAC-SHA256
signature of the request payload combined with the timestamp, compare it with the sent signature from Ticket Tailor, and verify they match.
We also recommend to invalidate webhooks received where the sent timestamp is older than 5 minutes.
See the code examples of how to verify webhook request.lients idempotent in case webhooks are sent more than once. You can track ids of already processed requests to not execute tasks more than once.
- php
- ruby
- python
$sharedSecret = 'ABCD123';
$body = file_get_contents('php://input');
$headerParts = explode(',', $_SERVER['HTTP_TICKETTAILOR_WEBHOOK_SIGNATURE']);
$timestamp = explode('=', $headerParts[0])[1];
$signature = explode('=', $headerParts[1])[1];
$hash = hash_hmac('sha256', $timestamp . $body, $sharedSecret);
if (! hash_equals($hash, $signature)) {
throw new \Exception('Invalid signature');
}
$tolerance = 60 * 5; // 5 minutes
if ((time() - $timestamp) > $tolerance) {
throw new \Exception('Webhook is out of date');
}
# Tested with ruby 3.2
sharedSecret = 'ABCD123'
headerParts = request.headers['TicketTailor-Webhook-Signature'].split(',')
timestamp = headerParts[0].split('=')[1]
signature = headerParts[1].split('=')[1]
hash = OpenSSL::HMAC.hexdigest(
OpenSSL::Digest.new('sha256'), sharedSecret, timestamp + request.body.string
)
signature = Digest::SHA256.hexdigest(signature)
hash = Digest::SHA256.hexdigest(hash)
# Perform a simple string comparison
if signature == hash
logger.info "Valid signature!"
else
logger.info "Invalid signature!"
end
tolerance = 60 * 5 # 5 minutes
if Time.now.to_i - timestamp.to_i > tolerance
logger.info 'Out of date'
end
sharedSecret = 'ABCD123'
headerParts = request.headers.get('TicketTailor-Webhook-Signature').split(',')
timestamp = headerParts[0].split('=')[1]
signature = headerParts[1].split('=')[1]
requestBody = request.data
newSignature = hmac.new(
key=sharedSecret,
msg=timestamp + requestBody,
digestmod=hashlib.sha256
)
if hmac.compare_digest(newSignature.hexdigest(), str(signature)):
logging.info("Valid signature")
else:
logging.error("Invalid signature")
tolerance = 60 * 5 # 5 minutes
if time.time() - int(timestamp) > tolerance:
logging.error("Out of date")