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
"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

