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-TeamAppX-Stayblox-SignatureX-Stayblox-Timestamp
Headers on platform → app event webhooks:
X-Stayblox-EventX-Stayblox-SignatureX-Stayblox-Timestamp
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.