Receiving Webhooks in Production
Everything here follows from two facts about how BlockWyre delivers: a delivery attempt times out after 30 seconds, and after 5 consecutive failures the target is disabled and stops receiving anything.
A handler that is merely slow eventually becomes a handler that receives nothing.
Acknowledge first, work afterwards
Verify the signature, answer 200, and do the real work outside the request.
Anything you do before responding is spending your 30 seconds.
app.post("/webhook", async (req, res) => {
await verifySignature(req); // fast, and must happen first
res.status(200).send("OK"); // acknowledge
await queue.add("process-webhook", req.body);
});
Expect the same webhook twice
A retry can arrive after your handler already succeeded — a response lost on the
way back looks identical to a handler that never ran. Deduplicate on
webhookId, which is stable across retries of the same event.
async function processWebhook(payload) {
const { webhookId } = payload;
if (await db.webhooks.findOne({ webhookId })) return;
await handleWebhookEvent(payload);
await db.webhooks.create({ webhookId, processedAt: new Date() });
}
Record the id in the same transaction as the work it guards. Recording it afterwards leaves a window where a crash costs you the event.
Reject stale deliveries
A valid signature proves a payload came from BlockWyre, not that it arrived just
now. X-BlockWyre-Timestamp carries the send time in Unix seconds; refuse
anything outside a window you choose.
const timestamp = parseInt(headers["x-blockwyre-timestamp"], 10);
const now = Math.floor(Date.now() / 1000);
if (Math.abs(now - timestamp) > 300) {
throw new Error("Webhook is outside the accepted time window");
}
Fail on the signature, not on your own bugs
These two failures deserve opposite answers:
| What failed | Respond | Why |
|---|---|---|
| Signature verification | 401 | The request is not ours. Retrying will not change that |
| Your own processing | 200 | Retrying replays a payload your code already could not handle, and five of those disable the target |
Answer 200 and route the failure to your own alerting. Spending delivery
attempts on a bug in your handler costs you unrelated events later.
app.post("/webhook", async (req, res) => {
try {
await verifySignature(req);
} catch {
return res.status(401).send("Unauthorized");
}
res.status(200).send("OK");
try {
await processWebhook(req.body);
} catch (error) {
logger.error("webhook processing failed", { error, body: req.body });
}
});
Watch the target, not just the handler
Your logs show what arrived. They cannot show what stopped arriving. Poll
GET /api/v2/webhooks/targets and alert on disabledAt appearing — that field
is the difference between a quiet day and a silently disabled integration.
Worth tracking as well: delivery success rate, handler processing time, and
X-BlockWyre-Retry-Count. A retry count climbing above zero is the warning
before the disable.
Testing locally
Targets must be reachable from the internet, so expose your local server with a tunnel:
ngrok http 3000
Register the tunnel URL as a target, and remember to delete it afterwards — an expired tunnel is a failing target, and five failures disable it.
Verify your signature code against the public JWKS endpoint before going near production. See Validating Webhooks.
Troubleshooting
| Symptom | Cause |
|---|---|
| Deliveries stopped entirely | The target was disabled after 5 failures. Check disabledAt, fix the endpoint, then re-enable it — see Managing Webhook Targets |
| Signature verification fails | The signature is over the raw body. A framework that parses and re-serializes JSON changes the bytes and invalidates it. Capture the raw body before any parsing middleware |
| Some events never arrive | Every target receives every event; there is no subscription. If an event is missing, it was not emitted — reconcile against the API |
X-BlockWyre-Retry-Count keeps climbing | Your endpoint is answering outside 2xx/3xx, or taking longer than 30 seconds |
| Nothing arrives at a new target | Confirm the URL is reachable from the public internet and presents a valid certificate |
Support and Resources
If you need assistance or have any questions, our support team is here to help. You can contact our support team at support@blockwyre.com.
Stay Updated
Stay up-to-date with the latest news, updates, and features from BlockWyre by following us on social media:
- Instagram: BlockWyre Instagram
- Twitter: BlockWyre Twitter
- Facebook: BlockWyre Facebook
- LinkedIn: BlockWyre LinkedIn
We are excited to have you on board and look forward to seeing how you leverage BlockWyre's powerful tools to enhance your financial operations. Happy integrating!