Request
- Method: POST
- Content-Type: application/json
- Header
X-Status-Webhook-Timestamp
: UNIX seconds - Header
X-Status-Webhook-Signature
:sha256=<hex>
Payload
{
"event": "status.created", // or "status.deleted"
"did": "did:plc:...",
"handle": null,
"status": "🙂", // created only
"text": "in a meeting", // optional
"uri": "at://...", // record URI
"since": "2025-09-10T16:00:00Z", // created only
"expires": null // created only
}
Verify signature
Compute HMAC-SHA256 over timestamp + "." + rawBody
using your secret. Compare to header (without the sha256=
prefix) with constant-time equality, and reject if timestamp is too old (e.g., > 5 minutes).
// Node (TypeScript)
import crypto from 'node:crypto';
function verify(req: any, rawBody: Buffer, secret: string): boolean {
const ts = req.headers['x-status-webhook-timestamp'];
const sig = String(req.headers['x-status-webhook-signature'] || '').replace(/^sha256=/, '');
if (!ts || !sig) return false;
const now = Math.floor(Date.now()/1000);
if (Math.abs(now - Number(ts)) > 300) return false; // 5m
const mac = crypto.createHmac('sha256', secret).update(String(ts)).update('.').update(rawBody).digest('hex');
return crypto.timingSafeEqual(Buffer.from(mac, 'hex'), Buffer.from(sig, 'hex'));
}
// Rust (axum-ish)
use hmac::{Hmac, Mac};
use sha2::Sha256;
fn verify(ts: &str, sig_hdr: &str, body: &[u8], secret: &str) -> bool {
let sig = sig_hdr.strip_prefix("sha256=").unwrap_or(sig_hdr);
if let Ok(ts_int) = ts.parse::() {
if (chrono::Utc::now().timestamp() - ts_int).abs() > 300 { return false; }
} else { return false; }
let mut mac = Hmac::::new_from_slice(secret.as_bytes()).unwrap();
mac.update(ts.as_bytes());
mac.update(b".");
mac.update(body);
let calc = hex::encode(mac.finalize().into_bytes());
subtle::ConstantTimeEq::ct_eq(calc.as_bytes(), sig.as_bytes()).into()
}
# Python (Flask example)
import hmac, hashlib, time
from flask import request
def verify(secret: str, raw_body: bytes) -> bool:
ts = request.headers.get('X-Status-Webhook-Timestamp')
sig_hdr = request.headers.get('X-Status-Webhook-Signature', '')
if not ts or not sig_hdr.startswith('sha256='):
return False
if abs(int(time.time()) - int(ts)) > 300:
return False
expected = hmac.new(secret.encode(), (ts + '.').encode() + raw_body, hashlib.sha256).hexdigest()
actual = sig_hdr[len('sha256='):]
return hmac.compare_digest(expected, actual)
// Go (net/http)
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"net/http"
"strconv"
"time"
)
func verify(r *http.Request, body []byte, secret string) bool {
ts := r.Header.Get("X-Status-Webhook-Timestamp")
sig := r.Header.Get("X-Status-Webhook-Signature")
if ts == "" || sig == "" { return false }
if len(sig) > 7 && sig[:7] == "sha256=" { sig = sig[7:] }
tsv, err := strconv.ParseInt(ts, 10, 64)
if err != nil || time.Now().Unix()-tsv > 300 || tsv-time.Now().Unix() > 300 { return false }
mac := hmac.New(sha256.New, []byte(secret))
mac.Write([]byte(ts))
mac.Write([]byte("."))
mac.Write(body)
expected := hex.EncodeToString(mac.Sum(nil))
return hmac.Equal([]byte(expected), []byte(sig))
}