Stayntouch: Webhook Signature Verification Guide
Overview
To ensure the authenticity and security of webhook requests from Stayntouch, all webhook payloads include an HMAC-SHA256 signature that you should validate before processing.
Getting Your Shared Secret
Your Stayntouch Onboarding team will configure webhook signature verification in your PMS and provide you with your unique shared signature secret. This secret is used to verify that webhook requests are genuinely from Stayntouch and haven't been tampered with.
Important: Keep your shared secret secure and never expose it in client-side code or public repositories.
How Signature Verification Works
Every webhook request from Stayntouch contains a signature in the snt-webhook-signature header. To verify the webhook's authenticity:
Use the request body exactly as received (raw payload)
Generate an HMAC-SHA256 hash using your shared secret and the payload
Encode the resulting hash using Base64
Compare the computed Base64-encoded hash with the
snt-webhook-signatureheader valueIf the values match, the request is authentic
To ensure the authenticity of webhook requests from Stayntouch, validate the HMAC-SHA256 signature included in each request’s header.
Every request contains a signature in the snt-webhook-signature header. Use your shared secret and the request body to verify this signature.
Testing the webhook payload validation
You can use the following shared secret and payload values to verify that your implementation is correct.
Payload (JSON):
{
"id": 203,
"object": "event",
"event": "edit_reservation",
"created_at": "2025-06-13T06:01:42Z",
"data": {
"id": 1,
"object": "reservation",
"hotel_id": 1,
"confirmation_number": "100000",
"arrival_date": "2025-06-12",
"arrival_time": "2025-06-13T01:00:00.000Z",
"departure_date": "2025-06-12",
"departure_time": "2025-06-13T03:00:00.000Z",
"status": "reserved"
}
}Shared Secret (generated by user): d1dc79f45a5df6e2b39888b1a734202c
If your implementation is correct, the signatures that you generate should match the following signature values:
signature:
CO5s3gPNVbTmnPq3zQM1/7JJCsB4mUDcZras3RZ+Zkw=snt-webhook-signature:CO5s3gPNVbTmnPq3zQM1/7JJCsB4mUDcZras3RZ+Zkw=
Implementation Examples
Below are code examples showing how to implement HMAC signature verification in various programming languages.
Ruby
require 'openssl'
require 'base64'
require 'active_support/security_utils'
def verify_hmac_signature(payload, signature, shared_secret)
hmac_hex = OpenSSL::HMAC.hexdigest('sha256', shared_secret, payload)
hmac_base64 = Base64.strict_encode64([hmac_hex].pack('H*'))
ActiveSupport::SecurityUtils.secure_compare(hmac_base64, signature)
end
# Controller Example
class WebhooksController < ApplicationController
skip_before_action :verify_authenticity_token
def receive
payload = request.raw_post
signature = request.headers['snt-webhook-signature']
shared_secret = ENV['STAYNTOUCH_WEBHOOK_SECRET']
if verify_hmac_signature(payload, signature, shared_secret)
# Process webhook
head :ok
else
head :unauthorized
end
end
endNode.js / JavaScript
const crypto = require('crypto');
function verifyHmacSignature(payload, signature, sharedSecret) {
const hmac = crypto.createHmac('sha256', sharedSecret)
.update(payload)
.digest('hex');
const hmacBase64 = Buffer.from(hmac, 'hex').toString('base64');
return signature === hmacBase64;
}
// Express.js example
app.post('/webhook', (req, res) => {
const payload = JSON.stringify(req.body);
const signature = req.headers['snt-webhook-signature'];
const sharedSecret = process.env.STAYNTOUCH_WEBHOOK_SECRET;
const isValid = verifyHmacSignature(payload, signature, sharedSecret);
if (isValid) {
// Process webhook
return res.status(200).send('OK');
}
return res.status(401).send('Invalid signature');
});Python
import hmac
import hashlib
import base64
def verify_hmac_signature(payload: str, signature: str, shared_secret: str) -> bool:
# Generate HMAC SHA256 digest
hmac_digest = hmac.new(
key=shared_secret.encode('utf-8'),
msg=payload.encode('utf-8'),
digestmod=hashlib.sha256
).digest()
# Convert to Base64
hmac_base64 = base64.b64encode(hmac_digest).decode('utf-8')
# Compare securely
return hmac.compare_digest(hmac_base64, signature)
# Flask example
import json
@app.route('/webhook', methods=['POST'])
def webhook():
# Convert the payload to a JSON string
payload = json.dumps(request.json, separators=(',', ':'))
signature = request.headers.get('snt-webhook-signature')
shared_secret = os.environ.get('STAYNTOUCH_WEBHOOK_SECRET')
if verify_hmac_signature(payload, signature, shared_secret):
# Process webhook
return 'OK', 200
else:
return 'Invalid signature', 401PHP
function verify_hmac_signature($payload, $signature, $sharedSecret) {
// Create HMAC SHA256 hash
$hash = hash_hmac('sha256', $payload, $sharedSecret, true);
// Encode to Base64
$hmacBase64 = base64_encode($hash);
// Constant-time comparison to prevent timing attacks
return hash_equals($hmacBase64, $signature);
}
// Usage
$payload = json_encode($payload, JSON_UNESCAPED_SLASHES);
$signature = $_SERVER['HTTP_SNT_WEBHOOK_SIGNATURE'];
$sharedSecret = getenv('STAYNTOUCH_WEBHOOK_SECRET');
if (verify_hmac_signature($payload, signature, $sharedSecret)) {
// Process webhook
echo "Verified";
} else {
http_response_code(401);
echo "Invalid signature";
}Best Practices
Store your shared secret securely in environment variables or a secrets manager
Always use the raw request body for signature verification (before any parsing or processing)
Use constant-time comparison functions to prevent timing attacks
Log failed verification attempts for security monitoring
Support
If you have questions about implementing webhook signature verification or need assistance:
Contact our Intagrations team on integrations@stayntouch.com
Refer to our webhook documentation: Stayntouch: Webhook Event Types Reference