The portal timeline is now available

Webhook Dashboards, Ready to Paste

The Repost portal's surfaces as blocks: delivery history, endpoint management, the dead-letter queue and the event catalog. Wired to your data, owned by you. Open Source.

Files
components/webhook-dlq.tsx
"use client";

import { RepostPortalProvider, type PortalClient, useDlq, useDlqCount, useReplayDelivery } from "@repost/portal-react";
import { useState } from "react";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { DlqPanel } from "@/components/dlq-panel";

export type WebhookDlqProps = {
  /** Return a portal access token minted by your backend. */
  getToken?: () => Promise<string>;
  /**
   * A pre-built client, instead of `getToken` — used by previews and tests to
   * run the block against fixtures.
   */
  client?: PortalClient;
  /** Narrow to one endpoint. */
  endpointId?: string;
};

/**
 * The dead-letter queue, wired: count badge, exhausted deliveries, and
 * one-click replay. Hooks at the top, props down.
 */
export function WebhookDlq({ getToken, endpointId, client }: WebhookDlqProps) {
  return (
    <RepostPortalProvider {...(client ? { client } : { getToken: getToken! })}>
      <WebhookDlqContent endpointId={endpointId} />
    </RepostPortalProvider>
  );
}

function WebhookDlqContent({ endpointId }: { endpointId?: string }) {
  const [replayingId, setReplayingId] = useState<string | null>(null);
  const dlq = useDlq({ endpointId });
  const count = useDlqCount({ endpointId });
  const replay = useReplayDelivery();

  return (
    <div className="flex flex-col gap-3">
      <header className="flex items-center gap-2">
        <h2 className="text-lg font-semibold">Dead-letter queue</h2>
        {count.data !== undefined && count.data > 0 && (
          <Badge variant="error" typography="mono">
            {count.data}
          </Badge>
        )}
      </header>

      <DlqPanel
        deliveries={(dlq.items ?? []).map((delivery) => ({
          id: delivery.id,
          type: delivery.type,
          endpointId: delivery.endpointId,
          attemptCount: delivery.attemptCount,
          lastStatusCode: delivery.lastStatusCode,
          lastErrorClass: delivery.lastErrorClass,
          createdAt: delivery.createdAt,
        }))}
        isLoading={dlq.isLoading}
        replayingId={replayingId}
        onReplay={(delivery) => {
          setReplayingId(delivery.id);
          replay
            .mutateAsync({ deliveryId: delivery.id })
            .finally(() => setReplayingId(null));
        }}
      />

      {dlq.hasNextPage && (
        <Button variant="outline" size="sm" disabled={dlq.isFetchingNextPage} onClick={dlq.fetchNextPage}>
          {dlq.isFetchingNextPage ? "Loading…" : "Load more"}
        </Button>
      )}
    </div>
  );
}
Dead-lettered deliveries with one-click replay.
webhook-dlq
webhook-dlq