That question is what webhook delivery retries exist to answer: when HTMLvault fires an event and your receiver does not respond, the platform tries again on a fixed schedule, records every attempt, and eventually stops trying and tells you why.
This guide walks the full path of one webhook — from a link view to a signed HTTP request to a retry queue — and the caveats that keep the whole thing honest. If you have not set webhooks up yet, start with Webhooks: Real-Time Link Events for Your Automation Stack and come back here for the delivery mechanics.
What a webhook is doing for you (and who needs one)
A webhook is an outbound HTTP POST that HTMLvault sends to a URL you own whenever something happens on one of your links. Instead of your systems asking "any views yet?" on a timer, HTMLvault tells them the moment it happens. Pro plans support up to five webhook endpoints.
Three audiences get real value from it:
- Sales and RevOps: a view event on a proposal link fires a task, a Slack ping, or a CRM activity record. Chip's "did they open it" question stops being a browser-refresh habit.
- Marketing: a campaign page view lands in your warehouse alongside channel attribution data, so the event and its source arrive together.
- IT and security: events land in your own log pipeline. When a link gets forwarded somewhere unexpected, the alert reaches your tooling without anyone logging into a vendor dashboard.
The delivery guarantee matters more than the payload. An event that arrives silently late is workable; an event that vanished without a trace is a data-integrity problem, and it is the reason retry behaviour is documented rather than implied.
The delivery path, step by step
Every webhook takes the same four-stage trip. Nothing about it is conditional on your plan or your endpoint's stack.
- Event occurs. Someone views a link, a password gate is passed, an expiry fires. HTMLvault builds a JSON payload describing what happened, to which link, at what timestamp.
- Payload is signed. HTMLvault computes an HMAC-SHA256 signature over the raw request body using your endpoint's signing secret, and sends it as a header alongside the POST. HMAC-SHA256 is a keyed hash: only a party holding the same secret can produce the same digest, which is how your receiver proves the request came from HTMLvault and was not modified in transit.
- Delivery is attempted. One HTTP POST to your URL. A 2xx response means delivered; anything else — 4xx, 5xx, timeout, TLS failure, DNS failure — counts as a failed attempt.
- Retry or disable. Failed attempts re-enter the queue on a backoff schedule. After five consecutive failures, the endpoint auto-disables and stops receiving events until you re-enable it.
How webhook delivery retries are scheduled
The backoff schedule is fixed and short enough to survive a deploy window without flooding your endpoint. After the initial attempt, HTMLvault waits 60 seconds, then 15 minutes, then 1 hour between retries. Each attempt is logged with its response code and timing.
Two properties of that schedule shape how you build against it.
The counter is consecutive, not cumulative. A successful delivery resets the failure count to zero. An endpoint that fails twice on Monday and twice on Thursday is never at risk; an endpoint that fails five times in a row is disabled. This is deliberate — an intermittent flake should not eventually disable a working integration, but a genuinely dead URL should stop generating traffic.
Auto-disable is a feature, not a failure. An endpoint that has been unreachable through the whole backoff window is almost never coming back on its own: the receiver was decommissioned, the token rotated, the tunnel closed. Continuing to POST at it burns your quota and buries the real signal in noise. HTMLvault stops, flags the endpoint, and waits for a human.
Verifying the signature: a worked example
Signature verification is the one piece of receiver code you should not skip. Your webhook URL is reachable by anyone who learns it, and a forged POST claiming "proposal viewed from Frankfurt" can quietly corrupt a CRM. Verifying the HMAC means only requests signed with your secret get through.
The pattern in every language is the same: take the raw request body before any JSON parsing, compute HMAC-SHA256 with your signing secret, and compare it to the signature header using a constant-time comparison. In Node:
const crypto = require("crypto");
function verify(rawBody, headerSignature, secret) {
const expected = crypto
.createHmac("sha256", secret)
.update(rawBody, "utf8")
.digest("hex");
const a = Buffer.from(expected);
const b = Buffer.from(headerSignature || "");
if (a.length !== b.length) return false;
return crypto.timingSafeEqual(a, b);
}
app.post("/htmlvault-hook", (req, res) => {
if (!verify(req.rawBody, req.get("x-htmlvault-signature"), process.env.HV_SECRET)) {
return res.sendStatus(401);
}
enqueue(JSON.parse(req.rawBody)); // hand off, then answer
res.sendStatus(200);
});
Two details in that snippet do most of the work. req.rawBody is the unparsed body — re-serializing parsed JSON changes whitespace and key order, and the hash will not match. And enqueue() runs before the response: the endpoint acknowledges fast and does its real processing off the request path, which is how you avoid timeouts that trigger retries you did not need.
Return 401 on a bad signature and HTMLvault treats it as a failed attempt, which is correct — a receiver rejecting your signature is a configuration problem worth surfacing, not something to silently absorb.
200, then spent nine seconds writing to a spreadsheet before doing anything else. The retries stopped. So did the spreadsheet, at 4,000 rows, on the afternoon a competitor tweeted the pricing page.Limits and caveats
Webhooks are simple until they are not. The failure modes worth designing around:
- Delivery is at-least-once, not exactly-once. If your endpoint processes a request and then times out before responding, HTMLvault sees a failure and retries — and you get the event twice. Make your handler idempotent: key on the event id and ignore duplicates.
- Ordering is not guaranteed. A retried event can land after a newer one. Sort by the payload timestamp, never by arrival order.
- Answer fast, work later. Anything slower than a couple of seconds risks a timeout. Acknowledge, queue, process.
- Retries pause your data, they do not backfill it. Once an endpoint auto-disables, events that fire while it is off are not queued for later delivery. Re-enable it and pull the gap from per-link analytics or the API, which retain the record regardless of webhook state.
- Rotate the secret deliberately. Deploy the new secret to your receiver first, then rotate in HTMLvault — a receiver verifying against a stale secret will 401 its way to five consecutive failures quickly.
- Do not put sensitive data in the URL. Auth belongs in the signature, not a query string that ends up in your own proxy logs. If your endpoint's payloads flow into a shared warehouse, the same scanning discipline you apply to shared HTML applies to what you log.
One deliberate constraint: HTMLvault does not chase an endpoint forever. Five consecutive failures, then a clean stop and a visible state change. Integrations that quietly retry into the void for days are how teams end up trusting a broken pipeline.
Setting up an endpoint that survives
The setup itself takes a few minutes.
- Stand the receiver up first and confirm it returns
200to an unsigned test POST. Fix reachability before you add crypto. - Add the endpoint in your HTMLvault webhook settings and copy the signing secret into your receiver's environment — not into source control.
- Turn on signature verification and send a test event. A
401here means the raw-body problem nine times out of ten. - Trigger a real view on a throwaway link and confirm the event lands, then confirm a duplicate of the same event id is ignored.
- Add a monitor for the auto-disable state. The point of a clean stop is that something notices it — a weekly glance at the delivery log counts, an alert is better.
Do that once and you get an integration whose failure mode is written down, bounded, and visible in a log rather than inferred from silence. If webhooks are part of a broader automation build, the REST API covers the inbound half of the same loop.
