IntegrationsGuides

Webhook Delivery Retries: How HTMLvault Sends a Link Event

HTMLvault Team·August 25, 2026·8 min read
Chip Bellfort's automation was supposed to post a Slack message the second a prospect opened his pricing page. It worked for three weeks, then went silent — and nobody could say whether the prospect had stopped opening the link or the endpoint had stopped answering the phone.

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.

  1. 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.
  2. 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.
  3. 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.
  4. 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.
The retry stage is dashed because a healthy endpoint never reaches it — webhook delivery retries are the exception path, not the normal one.

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.

Chip's Slack alert had gone quiet because the intern-built receiver it pointed at was retired in a sprint cleanup two weeks earlier. HTMLvault had tried five times, disabled the endpoint, and noted it plainly in the delivery log. Chip had spent those two weeks telling the pipeline review that the pricing page was "just not landing this quarter."
Retry attempt timeline with backoff intervals and auto-disable Five consecutive failures, then auto-disable ATTEMPT TIMELINE T+0 Attempt 1 502 +60S Attempt 2 502 +15MIN Attempt 3 TIMEOUT +1HR Attempt 4 TIMEOUT +1HR Attempt 5 TIMEOUT ENDPOINT AUTO-DISABLED ANY 2XX RESETS A single successful delivery at any point returns the failure count to zero.
Because the counter tracks consecutive failures, an endpoint that flakes once a week never disables — only one that stays down through the full backoff window does.

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.

Chip's replacement receiver answered every POST with a cheerful 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.

  1. Stand the receiver up first and confirm it returns 200 to an unsigned test POST. Fix reachability before you add crypto.
  2. Add the endpoint in your HTMLvault webhook settings and copy the signing secret into your receiver's environment — not into source control.
  3. Turn on signature verification and send a test event. A 401 here means the raw-body problem nine times out of ten.
  4. Trigger a real view on a throwaway link and confirm the event lands, then confirm a duplicate of the same event id is ignored.
  5. 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.

webhooksintegrationsautomationapirevopsdeveloper guide
HTMLvault

Share HTML securely — without losing your job.

The enterprise-grade platform for sharing HTML pages, reports, and dashboards with full PII scanning, access controls, and audit trails.

Start for free

Related Posts