# Test and replay deliveries

> Send a test event to your endpoint, see each delivery and how your endpoint answered, and send a failed one again.

This guide checks a webhook receiver end to end, sees how each delivery went, and sends a failed one again.

## Before you start

- **A webhook endpoint registered,** and its `endpointId`, as [Get a webhook when a run finishes](https://docs.kindgi.com/v0.1.8/guides/webhooks/receive-run-finished.md) shows.
- **A receiver listening at its URL,** such as one from [Verify a webhook](https://docs.kindgi.com/v0.1.8/guides/webhooks/verify-a-webhook.md).
- **Node 22.12 or later, Python 3.11 or later, or Java 17 or later,** with Kindgi 0.1's client, and `KINDGI_API_URL` and `KINDGI_API_TOKEN` set.

## 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:

- **TypeScript**

  `scripts/test-endpoint.ts`

  ```ts
  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);
  }
  ```

  ```text
  a0e05ab7-6cff-440b-b291-d03c0f4443c0 webhook.test pending
  delivered 1 204
  ```

- **Python**

  `scripts/test_endpoint.py`

  ```python
  import 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)
  ```

  ```text
  3b75488b-7dd0-4bfe-8abe-83a76b9e1bf2 webhook.test pending
  delivered 1 204
  ```

- **Java**

  `src/main/java/com/acme/app/TestEndpoint.java`

  ```java
  package 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]);
    }
  }
  ```

  ```text
  3fab48a3-50d2-4de7-a3a1-46822c41fc54 webhook.test pending
  delivered 1 204
  ```

- **Scala**

  `src/main/scala/acme/app/TestEndpoint.scala`

  ```scala
  package acme.app

  import com.kindgi.client.Kindgi
  import 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))
  }
  ```

  ```text
  09f002b1-3d30-43a5-9580-2683e6627868 webhook.test pending
  delivered 1 204
  ```

It 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:

```json
{
  "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

Each event sent to an endpoint is a **delivery**. `listDeliveries`, or `list_deliveries` in Python, shows them, newest first, with how the endpoint answered:

```json excerpt
{
  "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`:** `pending` while it waits for its next attempt, `delivered` once the endpoint answered 2xx, or `failed` once every attempt failed.
- **`lastResponseStatus`** and **`lastError`** say what went wrong. `lastError` is `connection-refused` when nothing listened at the URL, for example. It's `url-refused` when the deployment refuses the receiver's address, as [the `url`](https://docs.kindgi.com/v0.1.8/guides/webhooks/receive-run-finished.md#2-register-the-endpoint) describes.
- **`nextAttemptAt`** is when a pending delivery is tried again.

Pass `status: 'failed'`, or `status="failed"` in Python, to list only the ones that gave up.

## 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:

- **TypeScript**

  `scripts/fix-endpoint.ts`

  ```ts
  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);
  }
  ```

  ```text
  pending
  ```

- **Python**

  `scripts/fix_endpoint.py`

  ```python
  from 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)
  ```

  ```text
  pending
  ```

- **Java**

  `src/main/java/com/acme/app/FixEndpoint.java`

  ```java
  package 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]);
    }
  }
  ```

  ```text
  pending
  ```

- **Scala**

  `src/main/scala/acme/app/FixEndpoint.scala`

  ```scala
  package acme.app

  import com.kindgi.client.Kindgi
  import 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))
  }
  ```

  ```text
  pending
  ```

It 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

`unregister` stops the endpoint. Its deliveries that were still pending fail, and it no longer appears in `list`.

The [webhook endpoints reference](https://docs.kindgi.com/v0.1.8/reference/python/resources/webhook-endpoints.md) lists every operation.

## Next

- [Verify a webhook](https://docs.kindgi.com/v0.1.8/guides/webhooks/verify-a-webhook.md)
- [Webhook events and deliveries](https://docs.kindgi.com/v0.1.8/guides/webhooks/webhook-events.md)
- [Get a webhook when a run finishes](https://docs.kindgi.com/v0.1.8/guides/webhooks/receive-run-finished.md)
