HelloJohn / docs
Webhooks

Webhook Logs

View delivery history, inspect request and response payloads, filter by status, and diagnose webhook failures.

Webhook Logs

HelloJohn keeps a detailed log of every webhook delivery attempt. Use these logs to diagnose failures, verify delivery, and replay events.


Delivery Statuses

StatusDescription
succeededEndpoint responded with 2xx
failedEndpoint returned non-2xx or timed out
pendingInitial attempt in flight
permanently_failedAll retry attempts exhausted

Viewing Logs in the Dashboard

  1. Go to Developer → Webhooks
  2. Click your webhook endpoint
  3. Open the Deliveries tab

Each delivery shows:

  • Event type and delivery ID
  • Status and HTTP response code
  • Timestamp and response time
  • Request headers and body
  • Response body

Click any delivery to see the full payload and response, and to manually retry it.


Listing Deliveries via API

All Deliveries for a Webhook

curl "https://api.hellojohn.dev/v1/webhooks/wh_01HABCDEF777001/deliveries" \
  -H "Authorization: Bearer sk_live_abc123" \
  -H "X-Tenant-ID: tnt_01HABCDEF654321"

Filter by Status

curl "https://api.hellojohn.dev/v1/webhooks/wh_01HABCDEF777001/deliveries?status=failed" \
  -H "Authorization: Bearer sk_live_abc123" \
  -H "X-Tenant-ID: tnt_01HABCDEF654321"

Filter by Event Type

curl "https://api.hellojohn.dev/v1/webhooks/wh_01HABCDEF777001/deliveries?event=user.created" \
  -H "Authorization: Bearer sk_live_abc123" \
  -H "X-Tenant-ID: tnt_01HABCDEF654321"

Query Parameters

ParameterDescription
statusFilter by status: succeeded, failed, permanently_failed
eventFilter by event type (e.g., user.created)
sinceISO timestamp — return deliveries after this time
untilISO timestamp — return deliveries before this time
cursorPagination cursor
limitResults per page (default: 20, max: 100)

Response:

{
  "data": [
    {
      "id": "del_01HABCDEF888002",
      "webhook_id": "wh_01HABCDEF777001",
      "event": "user.created",
      "status": "failed",
      "http_status": 500,
      "attempts": 3,
      "response_time_ms": 2103,
      "next_retry_at": "2024-01-20T10:30:00Z",
      "created_at": "2024-01-20T08:15:00Z",
      "last_attempted_at": "2024-01-20T09:45:00Z"
    }
  ],
  "next_cursor": "cur_01HABCDEF...",
  "has_more": true
}

Getting a Single Delivery

Fetch the full details of a delivery, including request and response payloads:

curl "https://api.hellojohn.dev/v1/webhooks/deliveries/del_01HABCDEF888002" \
  -H "Authorization: Bearer sk_live_abc123" \
  -H "X-Tenant-ID: tnt_01HABCDEF654321"

Response:

{
  "id": "del_01HABCDEF888002",
  "webhook_id": "wh_01HABCDEF777001",
  "event": "user.created",
  "status": "failed",
  "http_status": 500,
  "attempts": 3,
  "next_retry_at": "2024-01-20T10:30:00Z",
  "created_at": "2024-01-20T08:15:00Z",
  "request": {
    "url": "https://example.com/webhooks/hellojohn",
    "method": "POST",
    "headers": {
      "Content-Type": "application/json",
      "HelloJohn-Signature": "sha256=abc123...",
      "HelloJohn-Delivery-ID": "del_01HABCDEF888002",
      "HelloJohn-Event": "user.created",
      "HelloJohn-Timestamp": "1705744500"
    },
    "body": {
      "id": "evt_01HABCDEF111002",
      "type": "user.created",
      "data": {
        "user": {
          "id": "usr_01HABCDEF123456",
          "email": "alice@example.com",
          "created_at": "2024-01-20T08:15:00Z"
        }
      },
      "created_at": "2024-01-20T08:15:00Z"
    }
  },
  "response": {
    "http_status": 500,
    "body": "Internal Server Error",
    "response_time_ms": 2103
  },
  "attempts_detail": [
    {
      "attempt": 1,
      "http_status": 500,
      "response_time_ms": 2103,
      "attempted_at": "2024-01-20T08:15:00Z"
    },
    {
      "attempt": 2,
      "http_status": 500,
      "response_time_ms": 1840,
      "attempted_at": "2024-01-20T08:20:00Z"
    },
    {
      "attempt": 3,
      "http_status": 500,
      "response_time_ms": 1950,
      "attempted_at": "2024-01-20T09:45:00Z"
    }
  ]
}

Retrying a Failed Delivery

Manually retry a failed delivery:

curl -X POST "https://api.hellojohn.dev/v1/webhooks/deliveries/del_01HABCDEF888002/retry" \
  -H "Authorization: Bearer sk_live_abc123" \
  -H "X-Tenant-ID: tnt_01HABCDEF654321"

Response:

{
  "id": "del_01HABCDEF888002",
  "status": "pending",
  "message": "Retry scheduled"
}

You can also retry from the dashboard — navigate to the delivery and click Retry.


Monitoring Delivery Health Programmatically

Poll delivery logs to detect failures and alert your team:

async function checkWebhookHealth(webhookId: string) {
  const res = await fetch(
    `https://api.hellojohn.dev/v1/webhooks/${webhookId}/deliveries?status=failed&limit=10`,
    {
      headers: {
        Authorization: `Bearer ${process.env.HELLOJOHN_SECRET_KEY}`,
        "X-Tenant-ID": process.env.HELLOJOHN_TENANT_ID!,
      },
    }
  );

  const { data } = await res.json();

  if (data.length > 0) {
    console.warn(`⚠️ ${data.length} failed deliveries for webhook ${webhookId}`);

    for (const delivery of data) {
      console.warn(
        `  - ${delivery.event} (${delivery.id}): HTTP ${delivery.http_status}, ` +
        `${delivery.attempts} attempts, next retry: ${delivery.next_retry_at}`
      );
    }

    // Alert your team
    await sendSlackAlert(`Webhook failures detected: ${data.length} events`);
  }
}

Log Retention

PlanRetention
Free3 days
Starter7 days
Pro30 days
Enterprise90 days

After retention expires, delivery logs are purged. Export logs to your own storage if you need longer retention.


Exporting Delivery Logs

Use pagination to export all deliveries within a time window:

async function exportDeliveries(webhookId: string, since: string) {
  const deliveries = [];
  let cursor: string | null = null;

  do {
    const params = new URLSearchParams({ since, limit: "100" });
    if (cursor) params.set("cursor", cursor);

    const res = await fetch(
      `https://api.hellojohn.dev/v1/webhooks/${webhookId}/deliveries?${params}`,
      {
        headers: {
          Authorization: `Bearer ${process.env.HELLOJOHN_SECRET_KEY}`,
          "X-Tenant-ID": process.env.HELLOJOHN_TENANT_ID!,
        },
      }
    );

    const { data, next_cursor, has_more } = await res.json();
    deliveries.push(...data);
    cursor = has_more ? next_cursor : null;
  } while (cursor);

  return deliveries;
}

// Export all deliveries from the last 30 days
const since = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString();
const logs = await exportDeliveries("wh_01HABCDEF777001", since);
console.log(`Exported ${logs.length} deliveries`);

Common Failure Reasons

HTTP StatusLikely CauseFix
401Signature verification failingCheck HELLOJOHN_WEBHOOK_SECRET; ensure raw body is used for HMAC
404Webhook path not foundVerify the URL path matches your route
408 / TimeoutHandler taking > 30sRespond immediately and process async
500Unhandled exception in handlerAdd try/catch; check your error logs
503Server down or overloadedCheck server health; scale up if needed

On this page