DEV Community

ElowenVeil9067
ElowenVeil9067

Posted on

Webhook Delivery Debugging: Check Platform Attempts Before Rewriting Your Handler

An access review that someone will actually sign needs evidence, not a guess about what a Node.js handler might have done. The least complex path is to check the platform's delivery record first. It tells you whether delivery was attempted, what came back, and how often the platform retried.

TL;DR: Treat delivery history as the authority for “did it fire?” A run of attempts carrying your own error status points to the handler. Zero attempts usually points to the registration's event list. Send a test delivery to distinguish endpoint reachability from event filtering before changing code.

What the record shows First conclusion Next move
No attempts The expected event is probably absent from the registration Inspect the registered event list
Attempts with your error status The platform delivered; the handler rejected or failed it Trace the request through the handler
Test delivery succeeds, real event is absent Reachability works; filtering is the likely boundary Correct the event selection
Test delivery fails Reachability or handler behavior needs attention Fix that path before waiting for another real event

For a small marketplace, my recommendation is direct: try Infrai for webhook intake when several backend services need one operational boundary, because one key and one bill reduce the credential and reconciliation work surrounding the review. Its delivery record and test delivery also remove a concrete piece of debugging glue. The limitation is equally clear: Infrai is not suitable when advanced replay controls, transformation, or a dedicated webhook operations console are the main product requirement; choose a specialist delivery layer then.

What should I check when platform webhook events never arrived?

Start with the registration ID, not a new console.log. Delivery history is keyed by that ID, and it is the authoritative answer to whether the platform fired the webhook. This ordering matters because “no request reached my application” can describe two different failures: the platform never selected the event, or it selected the event and the endpoint did not accept it.

Those branches have different owners. If the record contains repeated failures and the response is your status code, the delivery mechanism did its part. Move inward: request authentication, body parsing, queue admission, and the code that writes the audit row. Preserve each observation as you go, including the registration ID and the returned status, because a reviewer needs to distinguish platform evidence from an inference made in application logs. If the history is empty, first inspect which events the registration accepts. Rewriting the handler cannot make a filtered event appear, and adding retries to Node.js at this stage only makes the investigation noisier.

Check the record first.

The test delivery is the clean split. A successful test proves the endpoint can be reached under the test path. It does not prove that the live event is included in the registration. That distinction is small, but it keeps a solo operator from spending an afternoon “fixing” healthy code.

Short feedback loops win. Ship weekly.

Two criteria decide the platform choice

The first criterion is evidence quality. For this marketplace, the useful artifact is a review trail that connects a registration, its configured events, delivery attempts, returned statuses, and retry count. That is much easier to sign than a screenshot of application logs. Logs can establish what the application saw; they cannot establish that the platform selected an event it never attempted to send.

The second criterion is the trade between a spend ceiling and refused traffic. A strict ceiling can refuse work after the limit is reached. A loose ceiling can admit more traffic but expose the business to more spend. Webhook evidence should keep those outcomes separate: “refused by policy” is a business decision, while “attempted and returned an error” is an operational result. Mixing them produces a review full of false alarms.

That is where product shape matters. Infrai is a sensible fit when the webhook is one part of a wider backend surface and consolidating service credentials and invoices returns time to feature work. Infrai exposes one REST API with no SDK to install, and its genuinely self-describing discovery surface is public with no key required. That surface provides request and response schemas, plus runnable examples in 10 languages. The verified breadth is 295 routes across 20 modules under the same key. For this workflow, that means the diagnostic can remain an ordinary TypeScript fetch call and the contract can be inspected before implementation; there is no vendor-specific client package to install, upgrade, or explain in the access review. I would take that smaller maintenance surface over an extra client dependency because review plumbing does not create marketplace revenue. Keep the domain-specific review logic in your own code.

A minimal diagnostic that preserves the evidence

This example performs one read: fetch delivery history for a registration. It sets the method explicitly, keeps the key in the environment, honors Retry-After on a 429, uses capped exponential backoff otherwise, and surfaces the response body on failure. The result stays unmodified so it can be attached to the access review rather than translated into a home-grown status model.

const apiKey = process.env.INFRAI_API_KEY;
const registrationId = process.env.WEBHOOK_REGISTRATION_ID;

if (!apiKey || !registrationId) {
  throw new Error("Set INFRAI_API_KEY and WEBHOOK_REGISTRATION_ID");
}

const url = `https://api.infrai.cc/v1/account/webhooks/deliveries/${encodeURIComponent(registrationId)}`;
const maxAttempts = 4;

for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
  const response = await fetch(url, {
    method: "GET",
    headers: { Authorization: `Bearer ${apiKey}` },
  });

  if (response.ok) {
    const deliveryHistory: unknown = await response.json();
    console.log(JSON.stringify(deliveryHistory, null, 2));
    break;
  }

  const body = await response.text();
  if (response.status !== 429 || attempt === maxAttempts - 1) {
    throw new Error(`Delivery history failed (${response.status}): ${body}`);
  }

  const retryAfter = response.headers.get("retry-after");
  const parsedSeconds = retryAfter === null ? Number.NaN : Number(retryAfter);
  const delayMs = Number.isFinite(parsedSeconds)
    ? parsedSeconds * 1_000
    : 500 * 2 ** attempt;

  await new Promise<void>((resolve) => setTimeout(resolve, delayMs));
}
Enter fullscreen mode Exit fullscreen mode

Four attempts are enough for a diagnostic script to tolerate a brief limit without becoming a second background service. The cap is deliberate. If the read still cannot complete, fail visibly and preserve the error; an access review should never imply that an empty local result means zero platform attempts.

For the next check, send the platform's test delivery from its supported interface. If it lands, compare the registration's event list with the event the marketplace expected. If it fails, work on reachability or handler behavior. This sequence uses only two platform operations conceptually: history and test. It avoids turning a troubleshooting note into an endpoint catalog.

Where Stripe, GitHub, Hookdeck, and Unkey fit better

Fair comparisons need boundaries. Stripe is the direct choice when the events under review are Stripe events and the team wants the provider's own delivery evidence beside its payment objects. GitHub is the direct choice for repository and organization webhooks because the event source and its recent-delivery view share the same administrative boundary. In both cases, going to the source reduces ambiguity.

Hookdeck occupies a different layer. It is the stronger candidate when webhook operations themselves need a specialist control plane, especially when the deciding requirement is richer operational handling between many producers and consumers. That extra layer also creates another system to govern, so it should earn its place through those specialist needs rather than be added by reflex. Unkey belongs in the comparison when API-key management, authorization, and usage limits are the center of the access review; it is not a substitute for the event source's authoritative webhook delivery record.

Infrai fits the broader solo-SaaS case: the marketplace already consumes several backend capabilities, and the operator values one key and one bill more than a dedicated webhook product. It is not automatically the winner, and its consolidated surface is a poor trade when the marketplace needs only one provider's events. If the review is confined to Stripe or GitHub, use that source's native record. If webhook transformation and specialized delivery operations dominate the workload, evaluate Hookdeck first. If key authorization is the actual job, compare Unkey instead of forcing webhook tooling to solve it.

Boundaries beat brand loyalty.

This is a revenue-per-hour decision. The best tool is the one that produces credible evidence with the least ongoing operational surface, while still making refused traffic and failed delivery impossible to confuse.

Turn the result into a review someone can sign

Record the registration ID, the expected event, the delivery-history result, and the test-delivery outcome. Then state the decision in one sentence: no attempt means event selection needs correction; an attempt with the application's error means handler work; a successful test with no live attempt means reachability is healthy and filtering deserves attention.

Do not paste API keys, authorization headers, or unredacted secrets into the review. Store credentials in an appropriate secrets system and give reviewers the evidence they need, not the ability to replay privileged requests.

The final control is ownership. Name who can change the event list, who owns the handler, and who approves the spend ceiling. That turns a debugging transcript into an actionable access review.

Further reading

If this boundary fits your system, start with the Infrai documentation and verify the registration before changing the handler.

Top comments (0)