Redelivery

Manually re-enqueue a webhook event after fixing a receiver bug. Same event.id, dedupe by it.

mb.events.redeliver(eventId) re-enqueues a single delivery for another attempt. Use it after fixing a bug in your receiver, or after dropping an event you didn’t process correctly.

Usage

server/scripts/redeliver-event.ts
import { Medblocks } from "medblocks";

const mb = new Medblocks(process.env.MEDBLOCKS_API_KEY!);

const evt = await mb.events.redeliver("evt_01J9YR9N3X4VZ6P2K5RH7M3LMP");
console.log(evt.attempts, evt.last_status_code, evt.delivered_at);

The returned WebhookEventRecord reflects state after the redelivery attempt, with updated attempts, delivered_at if it succeeded, last_status_code, last_response_body, and last_redelivered_at.

Same event.id Is Reused

Redelivery does not generate a new event id. Your receiver must dedupe by event.id. Otherwise the redelivery applies whatever side effect the original would have.

server/webhook-handler.ts
import type { WebhookEvent } from "medblocks";

async function handle(event: WebhookEvent) {
  const seen = await store.get(`evt:${event.id}`);
  if (seen) return;

  await applySideEffect(event);
  await store.set(`evt:${event.id}`, "ok", { ttl: 90 * 24 * 60 * 60 });
}

90 days covers the longest plausible window. Medblocks’ retry schedule runs ~3.6 days; manual redelivery extends that, but anything beyond 90 days is almost certainly a real bug worth investigating.

Rate Limit

Redelivery is rate-limited to one call per minute per event. A second call within 60 seconds throws a MedblocksError with type rate_limit_error and the Retry-After header parsed onto err.retryAfter.

server/scripts/redeliver-with-backoff.ts
import { Medblocks, isMedblocksError } from "medblocks";

const mb = new Medblocks(process.env.MEDBLOCKS_API_KEY!);

try {
  await mb.events.redeliver(eventId);
} catch (err) {
  if (isMedblocksError(err) && err.type === "rate_limit_error") {
    console.log(`retry after ${err.retryAfter ?? 60}s`);
  } else {
    throw err;
  }
}

For bulk replay after a bad deploy, walk the events list with a sleep between calls. See below.

Finding Events To Redeliver

Use mb.webhooks.listEvents to list deliveries for a specific endpoint, then redeliver the ones you need.

server/scripts/replay-failed-on-endpoint.ts
import { Medblocks } from "medblocks";

const mb = new Medblocks(process.env.MEDBLOCKS_API_KEY!);
const ENDPOINT_ID = "wh_01J9YR9N3X4VZ6P2K5RH7M3LMP";

for await (const evt of mb.webhooks.listEvents(ENDPOINT_ID).autoPagingIterator()) {
  if (evt.delivered_at !== null) continue;             // already delivered
  if (evt.last_status_code !== 500) continue;          // only replay the 500s

  await mb.events.redeliver(evt.id);
  await new Promise((r) => setTimeout(r, 1100));       // respect the 1/min limit
}

Filter by whichever fields fit your situation.

  • delivered_at !== null → already delivered, skip.
  • next_attempt_at !== null → still retrying on its own, no need to force.
  • last_status_code → narrow to the failure mode you fixed.
  • attempts → events that exhausted are terminal until you redeliver them.

When To Redeliver

  • After fixing a bug that caused your receiver to 5xx or 400.
  • After reactivating a disabled endpoint, to catch up on events whose retries already exhausted during the outage.
  • After a downstream system that depends on your webhook handler comes back up.

When Not To

  • During the active retry window. Medblocks will retry on its own. Manual redelivery skips the back-off and burns the per-minute rate limit for no reason.
  • For events you simply don’t want to handle. There is no “discard” call; just return 2xx and dedupe-by-id will keep you idempotent.

Errors

error.typeWhen
not_found_errorThe evt_* id does not exist for your organization.
rate_limit_errorAlready redelivered within the last 60 s. Inspect err.retryAfter.

Need help?

Running into an issue? Get support from our team and we will get back to you.

How is this guide?