Test and replay deliveries
This guide checks a webhook receiver end to end, sees how each delivery went, and sends a failed one again.
Before you start
Section titled “Before you start”- A webhook endpoint registered, and its
endpointId, as Get a webhook when a run finishes shows. - A receiver listening at its URL, such as one from Verify a webhook.
- Node 22.12 or later, Python 3.11 or later, or Java 17 or later, with
Kindgi 0.1's client, and
KINDGI_API_URLandKINDGI_API_TOKENset.
Send a test event
Section titled “Send a test event”A test event checks the receiver end to end, without a run.
sendTest, or send_test in Python, queues a signed webhook.test event
for an endpoint. Ask your coding agent: "I want to send my webhook endpoint a
test event and see whether it arrived." For reference, this function sends
one, then reads how it was delivered:
import { createClient } from '@kindgi/sdk/client';
const kindgi = createClient(); // KINDGI_API_URL, KINDGI_API_TOKEN
/** Sends the endpoint a test event, and prints how it was delivered. */export async function testEndpoint(endpointId: string) { const queued = await kindgi.webhookEndpoints.sendTest(endpointId); console.log(queued.deliveryId, queued.event.type, queued.status);
// A few seconds later await new Promise((resolve) => setTimeout(resolve, 5000)); const { data } = await kindgi.webhookEndpoints.listDeliveries(endpointId, { limit: 1 }); console.log(data[0]?.status, data[0]?.attempts, data[0]?.lastResponseStatus);}a0e05ab7-6cff-440b-b291-d03c0f4443c0 webhook.test pendingdelivered 1 204import time
from kindgi.client import Kindgi
kindgi = Kindgi() # KINDGI_API_URL, KINDGI_API_TOKEN
def test_endpoint(endpoint_id: str) -> None: """Sends the endpoint a test event, and prints how it was delivered.""" queued = kindgi.webhook_endpoints.send_test(endpoint_id) print(queued.delivery_id, queued.event.type, queued.status)
time.sleep(5) # a few seconds later delivery = kindgi.webhook_endpoints.list_deliveries(endpoint_id, limit=1).data[0] print(delivery.status, delivery.attempts, delivery.last_response_status)3b75488b-7dd0-4bfe-8abe-83a76b9e1bf2 webhook.test pendingdelivered 1 204package com.acme.app;
import com.kindgi.client.Kindgi;import com.kindgi.client.models.WebhookDelivery;import com.kindgi.client.resources.WebhookEndpointsListDeliveriesParams;
public final class TestEndpoint { private static final Kindgi kindgi = Kindgi.create(); // KINDGI_API_URL, KINDGI_API_TOKEN, or the running kindgi dev
/** Sends the endpoint a test event, and prints how it was delivered. */ public static void testEndpoint(String endpointId) throws InterruptedException { WebhookDelivery queued = kindgi.webhookEndpoints().sendTest(endpointId); System.out.println(queued.deliveryId() + " " + queued.event().type() + " " + queued.status());
Thread.sleep(5000); // a few seconds later WebhookDelivery delivery = kindgi.webhookEndpoints() .listDeliveries(endpointId, WebhookEndpointsListDeliveriesParams.builder().limit(1L).build()) .data().get(0); System.out.println(delivery.status() + " " + delivery.attempts() + " " + delivery.lastResponseStatus()); }
public static void main(String[] args) throws InterruptedException { testEndpoint(args[0]); }}3fab48a3-50d2-4de7-a3a1-46822c41fc54 webhook.test pendingdelivered 1 204package acme.app
import com.kindgi.client.Kindgiimport com.kindgi.client.resources.WebhookEndpointsListDeliveriesParams
object TestEndpoint { private val kindgi = Kindgi.create() // KINDGI_API_URL, KINDGI_API_TOKEN, or the running kindgi dev
/** Sends the endpoint a test event, and prints how it was delivered. */ def testEndpoint(endpointId: String): Unit = { val queued = kindgi.webhookEndpoints().sendTest(endpointId) println(s"${queued.deliveryId} ${queued.event.`type`} ${queued.status}")
Thread.sleep(5000) // a few seconds later val delivery = kindgi.webhookEndpoints() .listDeliveries(endpointId, WebhookEndpointsListDeliveriesParams.builder().limit(1L).build()) .data.get(0) println(s"${delivery.status} ${delivery.attempts} ${delivery.lastResponseStatus}") }
def main(args: Array[String]): Unit = testEndpoint(args(0))}09f002b1-3d30-43a5-9580-2683e6627868 webhook.test pendingdelivered 1 204It prints the queued delivery, then its outcome: delivered at the first
attempt, with the receiver's 204. The test event is signed like any other:
{ "id": "c1214e78-694f-401d-8b92-e374f0846a88", "type": "webhook.test", "createdAt": "2026-10-03T20:16:35.159Z", "data": { "endpointId": "9c0b9504-c5a6-439f-a5f9-e755f1c5e6e9" }}See the deliveries
Section titled “See the deliveries”Each event sent to an endpoint is a delivery. listDeliveries, or
list_deliveries in Python, shows them, newest first, with how the endpoint
answered:
{ "deliveryId": "6ca19f2b-0c57-481e-a768-ef06283898c0", "endpointId": "9c0b9504-c5a6-439f-a5f9-e755f1c5e6e9", "event": { "id": "f2b45827-da3c-4f4e-95ff-9422f0de0e13", "type": "run.finished", … }, "status": "delivered", "attempts": 1, "nextAttemptAt": null, "lastAttemptAt": "2026-10-03T20:15:32.194Z", "lastResponseStatus": 204, "lastError": null, "createdAt": "2026-10-03T20:15:32.095Z", "deliveredAt": "2026-10-03T20:15:32.194Z"}status:pendingwhile it waits for its next attempt,deliveredonce the endpoint answered 2xx, orfailedonce every attempt failed.lastResponseStatusandlastErrorsay what went wrong.lastErrorisconnection-refusedwhen nothing listened at the URL, for example. It'surl-refusedwhen the deployment refuses the receiver's address, as theurldescribes.nextAttemptAtis when a pending delivery is due to be tried again. The attempt starts within about 30 seconds of it.
Pass status: 'failed', or status="failed" in Python, to list only the
ones that gave up.
Fix and send again
Section titled “Fix and send again”A delivery sent again now, after you fix its cause, doesn't wait for its next attempt.
When an endpoint's URL is wrong, its deliveries stay pending. The first
attempt's lastError is connection-refused when nothing listens there, and
nextAttemptAt is a minute later. Fix the URL, and send the delivery again
now. Ask your coding agent: "I want to fix my webhook endpoint's URL and send
its delivery again now." For reference, this function does both, for an
endpoint and one of its deliveries:
import { createClient } from '@kindgi/sdk/client';
const kindgi = createClient(); // KINDGI_API_URL, KINDGI_API_TOKEN
/** Points the endpoint at the right URL, and sends the delivery again now. */export async function fixEndpoint(endpointId: string, deliveryId: string) { await kindgi.webhookEndpoints.update(endpointId, { url: 'http://localhost:8787/hooks/kindgi' }); const again = await kindgi.webhookEndpoints.redeliver(endpointId, deliveryId); console.log(again.status);}pendingfrom kindgi.client import Kindgi
kindgi = Kindgi() # KINDGI_API_URL, KINDGI_API_TOKEN
def fix_endpoint(endpoint_id: str, delivery_id: str) -> None: """Points the endpoint at the right URL, and sends the delivery again now.""" kindgi.webhook_endpoints.update(endpoint_id, url="http://localhost:8788/hooks/kindgi") again = kindgi.webhook_endpoints.redeliver(endpoint_id, delivery_id) print(again.status)pendingpackage com.acme.app;
import com.kindgi.client.Kindgi;import com.kindgi.client.models.PatchWebhookEndpointBody;import com.kindgi.client.models.WebhookDelivery;
public final class FixEndpoint { private static final Kindgi kindgi = Kindgi.create(); // KINDGI_API_URL, KINDGI_API_TOKEN, or the running kindgi dev
/** Points the endpoint at the right URL, and sends the delivery again now. */ public static void fixEndpoint(String endpointId, String deliveryId) { kindgi.webhookEndpoints().update(endpointId, PatchWebhookEndpointBody.builder().url("http://localhost:8787/hooks/kindgi").build()); WebhookDelivery again = kindgi.webhookEndpoints().redeliver(endpointId, deliveryId); System.out.println(again.status()); }
public static void main(String[] args) { fixEndpoint(args[0], args[1]); }}pendingpackage acme.app
import com.kindgi.client.Kindgiimport com.kindgi.client.models.PatchWebhookEndpointBody
object FixEndpoint { private val kindgi = Kindgi.create() // KINDGI_API_URL, KINDGI_API_TOKEN, or the running kindgi dev
/** Points the endpoint at the right URL, and sends the delivery again now. */ def fixEndpoint(endpointId: String, deliveryId: String): Unit = { kindgi.webhookEndpoints().update(endpointId, PatchWebhookEndpointBody.builder().url("http://localhost:8787/hooks/kindgi").build()) val again = kindgi.webhookEndpoints().redeliver(endpointId, deliveryId) println(again.status) }
def main(args: Array[String]): Unit = fixEndpoint(args(0), args(1))}pendingIt prints pending: the delivery is queued again. A few seconds later, it's
delivered. update also changes an endpoint's events, filter,
secretRef and description.
Stop receiving events
Section titled “Stop receiving events”unregister stops the endpoint. Its deliveries that were still pending fail,
and it no longer appears in list.
The webhook endpoints reference lists every operation.