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/connect";

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: 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/connect";

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 returns MedblocksRateLimitError with the Retry-After header parsed onto error.retryAfter.

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

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

try {
  await mb.events.redeliver(eventId);
} catch (err) {
  if (err instanceof MedblocksRateLimitError) {
    console.log(`retry after ${err.retryAfter}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/connect";

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

SubclassWhen
MedblocksNotFoundErrorThe evt_* id does not exist for your organization.
MedblocksRateLimitErrorAlready redelivered within the last 60 s. Inspect err.retryAfter.