Appearance
Signing & security
Every signed exchange between Stayblox and your app (outbound session calls and event webhooks) uses the same HMAC scheme. Verify what Stayblox sends you, and sign what you send back the same way.
Algorithm: HMAC-SHA256, keyed with the install's webhook secret.
Signed string: {timestamp}.{body} — the request timestamp, a literal dot, then the raw JSON body.
Signature header value: sha256=<hex digest>
Headers on platform → app session calls:
X-Stayblox-TeamX-Stayblox-SignatureX-Stayblox-Timestamp
Headers on platform → app event webhooks:
X-Stayblox-SignatureX-Stayblox-TimestampX-Stayblox-Event-IdX-Stayblox-TopicX-Stayblox-App
Verify an inbound request
Reconstruct the signature from the timestamp header and the raw request body (do not re-serialize JSON), then compare in constant time. Reject stale requests to bound replays.
PHP
php
function verifyStaybloxSignature(string $rawBody): void
{
$timestamp = $_SERVER['HTTP_X_STAYBLOX_TIMESTAMP'] ?? '';
$signature = $_SERVER['HTTP_X_STAYBLOX_SIGNATURE'] ?? '';
$secret = getenv('STAYBLOX_WEBHOOK_SECRET');
$expected = 'sha256=' . hash_hmac('sha256', $timestamp . '.' . $rawBody, $secret);
if (! hash_equals($expected, $signature)) {
http_response_code(401);
exit;
}
// Reject requests older than 5 minutes.
if (abs(time() - (int) $timestamp) > 300) {
http_response_code(401);
exit;
}
}Node.js
js
import crypto from 'node:crypto'
export function verifyStaybloxSignature(rawBody, headers, secret) {
const timestamp = headers['x-stayblox-timestamp'] ?? ''
const signature = headers['x-stayblox-signature'] ?? ''
const expected =
'sha256=' + crypto.createHmac('sha256', secret).update(`${timestamp}.${rawBody}`).digest('hex')
const ok =
signature.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))
if (!ok) throw new Error('Invalid signature')
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) throw new Error('Stale request')
}Notes
- Always verify against the raw body bytes, before any JSON parsing.
- Use a constant-time comparison (
hash_equals/crypto.timingSafeEqual). - The webhook secret is per install; key your verification by the
X-Stayblox-Appheader so one server can serve many hosts. - Your bearer token (for calling our GraphQL API) is separate from the webhook secret; never send the token to anyone but Stayblox.