Webhook signing
Verify 0Bit webhook signatures using the raw body, reject forged deliveries, dedupe event ids, and replay safely.
Webhook signing protects your backend from forged events. A valid webhook proves the event body was signed with your webhook secret for the relevant environment. It does not remove the need to validate object state, dedupe event ids, and reconcile with trusted API reads when necessary.
Use the raw body
Signature verification must run against the exact bytes sent to your endpoint. If your framework parses JSON first, verification can fail even when the event is legitimate.
Verification flow
Express example
import express from "express";
import { GateClient } from "@0bit/gate";
const gate = new GateClient({ apiKey: process.env.OBIT_SECRET_KEY! });
const app = express();
app.post("/webhooks/0bit", express.raw({ type: "application/json" }), (req, res) => {
try {
const event = gate.webhooks.constructEvent(
req.body,
req.header("Gate-Signature"),
process.env.OBIT_WEBHOOK_SECRET!,
);
// Persist event.id and the product object state before acknowledging.
res.sendStatus(200);
} catch {
res.status(400).send("invalid signature");
}
});What to verify
| Check | Why |
|---|---|
| Header exists | Missing signatures should not reach business logic. |
| Signature matches raw body | Confirms the payload was signed by 0Bit for this endpoint. |
| Timestamp freshness, where provided | Reduces replay-window risk. |
| Environment secret | Sandbox events must use sandbox whsec_*; live events must use live whsec_*. |
| Event id uniqueness | Prevents duplicate fulfillment during retries or replay. |
| Object status | Ensures your state change matches the current product object. |
Framework notes
| Framework pattern | Implementation detail |
|---|---|
| Express | Use express.raw({ type: "application/json" }) only on the webhook route. |
| Next.js route handler | Read await request.text() or bytes before parsing JSON. |
| API gateway | Disable body rewriting/compression on the webhook path if it changes signed bytes. |
| Queue-first handler | Verify at the edge, then enqueue the verified raw event or normalized event record. |
Dedupe table
Store a small event ledger in your application database.
| Column | Purpose |
|---|---|
event_id | Unique dedupe key. |
event_type | Routes the handler. |
object_id | Joins to session, quote, trade, payment, settlement, or transaction records. |
received_at | Audit and replay diagnostics. |
processed_at | Confirms durable handling completed. |
status | received, processed, ignored_duplicate, failed, or your equivalent. |
request_id | Support traceability when present. |
Returning 2xx without storing the event makes replay and support investigation much harder. Store first, then trigger downstream work.
Replay behavior
Webhook replay is for recovery, not for changing business state twice. Your handler must be safe when a previously processed event is replayed.
| Replay case | Correct behavior |
|---|---|
| Event already processed | Return 2xx and do not run fulfillment twice. |
| Event received but not processed | Resume processing from the durable event record. |
| Event invalid | Return 400; replaying an invalid event should not make it valid. |
| Object state has advanced | Read trusted object state and apply only the valid transition. |
Security rules
- Keep
whsec_*values in server secrets only. - Do not log webhook secrets or full signature headers.
- Do not store raw PII-heavy payloads unless policy requires it.
- Rotate webhook secrets if an endpoint or log sink is exposed.
- Use separate webhook secrets for sandbox and live.
- Treat browser callbacks as untrusted for fulfillment.
- Monitor dead-lettered deliveries and verification failures.