Skip to main content
Every outbound webhook Xobito sends is signed with HMAC-SHA256. Verifying the signature protects you from spoofed requests and from accidentally processing unsigned traffic.

Signature header

HeaderAlgorithmSigned over
X-Webhook-SignatureHMAC-SHA256, lowercase hexThe raw JSON request body, byte-for-byte
The header value is a hex-encoded HMAC-SHA256 digest — no sha256= prefix, no timestamps.

Secret

The signing secret is a shared value between Xobito and your endpoint. It is configured on the Xobito side via an environment variable on the workspace. Contact Xobito support if you need your secret issued or rotated.
Keep the secret out of source control and client-side code. Store it in an environment variable or secrets manager on your webhook receiver.

Verifying a request

You must verify using the raw body bytes, not a re-serialised JSON string. Many frameworks parse JSON before your handler runs — use the framework’s raw-body hook.
import crypto from "crypto";
import express from "express";

const app = express();
const SECRET = process.env.XOBITO_WEBHOOK_SECRET;

// IMPORTANT: capture the raw body for signature verification
app.use(express.raw({ type: "application/json" }));

app.post("/webhook", (req, res) => {
  const sig = req.header("X-Webhook-Signature");
  const expected = crypto
    .createHmac("sha256", SECRET)
    .update(req.body) // Buffer (raw bytes)
    .digest("hex");

  const ok =
    sig &&
    sig.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));

  if (!ok) return res.status(401).send("Invalid signature");

  const payload = JSON.parse(req.body.toString("utf8"));
  // handle payload.event + payload.model ...
  res.sendStatus(200);
});
Always use a constant-time comparison (crypto.timingSafeEqual, hmac.compare_digest, hash_equals). A naive == is vulnerable to timing attacks.

Retry-aware handlers

Xobito retries up to 3 total attempts with 0s, 2s, and 4s backoff. If your endpoint is slow or flaps, you may receive the same event more than once.
Deduplicate by (model, data.id, event, timestamp) — that tuple is stable across retries.
Return 2xx as soon as you have persisted (or enqueued) the event. Heavy work belongs in a background job.
Return the same 2xx on a duplicate so Xobito stops retrying.

Optional hardening

  • HTTPS only. Reject http:// at the load balancer — signatures are not a substitute for transport encryption.
  • Narrow scope. Only listen for the events you actually handle (contacts_actions, status_actions, source_actions in Settings → Webhook Settings).
  • Logs. Keep at least 30 days of request logs on your side — Xobito’s webhook_logs are purged after 30 days.

Troubleshooting

Double-check you are hashing the raw request body, not a pretty-printed or re-serialised JSON string. Even whitespace differences will break HMAC.
Some proxies strip non-standard headers. Configure your ingress (Nginx, Cloudflare, etc.) to pass the header through unchanged.
That is expected on retries after a slow or failed first attempt. Deduplicate on (model, data.id, event, timestamp).
Contact Xobito support. There is no self-service rotation in the current version.