Subscribe to a full resource (hull.*) or a specific event (hull.price_changed). Fan out to as many endpoints as you need — delivery is independent per endpoint.
We POST a JSON body to your URL with three signing headers. Respond with any 2xx within 10 seconds. Anything else triggers retry.
We retry any non-2xx or timeout with exponential backoff.
Always verify the signature before processing. Constant-time comparison prevents timing attacks. Reject requests older than 5 minutes to prevent replay.
// Express middleware
import { createHmac, timingSafeEqual } from 'crypto';
export function verify(req, secret) {
const sig = req.headers['boateros-signature'];
const ts = req.headers['boateros-timestamp'];
const payload = ts + '.' + req.rawBody;
const mac = createHmac('sha256', secret)
.update(payload).digest('hex');
return timingSafeEqual(
Buffer.from(mac), Buffer.from(sig));
} # Flask view decorator
import hmac, hashlib
from flask import request, abort
def verify(secret: str) -> bool:
sig = request.headers["Boateros-Signature"]
ts = request.headers["Boateros-Timestamp"]
payload = f"{ts}.{request.data.decode()}"
mac = hmac.new(secret.encode(),
payload.encode(),
hashlib.sha256).hexdigest()
return hmac.compare_digest(mac, sig) The dashboard shows the last 30 days of delivery attempts for each endpoint. Filter by event type, status, or time range. Replay a single event or replay everything since a given timestamp.
Including the status code we got back, the response body, and the latency.
Click replay. We resend with a new timestamp and signature, same payload.
For rebuilding state after an outage. Pick a moment, we redeliver everything since.
Dual-key rotation with a 7-day overlap window. No downtime.
After 72 hours of failure the event moves to your dead-letter queue. Your endpoint is marked unhealthy. You get an email and a Slack alert if configured.
Events we couldn't deliver in 72h. Kept for 30 days. Drain via API.
Failure rate over 10% for 15 minutes → email, Slack, and PagerDuty hooks.
100% failure for 30m pauses delivery. Manually re-enable from the dashboard.