Debug Webhooks with Delivery Logs (Message Log Workflow)

Debug Webhook Deliveries with Message Log

If you are asking how to debug missing webhook deliveries using delivery logs, start with a strict sequence: ingestion, delivery run, then endpoint response details. That path isolates failures quickly and avoids guessing.

This guide gives you a practical Message Log workflow for Laravel/PHP integrations. You will confirm event ingestion, inspect delivery outcomes, and trace why requests fail or appear to succeed without processing.

Assumption: your webhook endpoint already performs signature verification with raw-body HMAC and constant-time hash_equals comparison.

The debugging order of operations (fastest to slowest)

Confirm the event was ingested (API 201)

First confirm ingestion returned 201. If ingestion failed, there is no delivery run to debug yet. Use How to send a test event and confirm delivery runs.

Confirm a webhook delivery run exists

Open Message Log and find the run tied to your test event. Confirm attempt timestamps and retry count before checking app logs.

Confirm your endpoint response (2xx vs non-2xx)

Use delivery attempts to verify what your endpoint returned on each try. Align expected behavior with What counts as success (2xx) and how retries work.

A) Create a delivery run (SendPromptly ingestion)

Use the Getting Started curl to ingest an event (replace token).

1
2
3
4
5
6
7
8
9
curl -X POST https://app.sendpromptly.com/api/v1/events \
  -H "Authorization: Bearer sp_dev_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Idempotency-Key: sample-001" \
  -H "Content-Type: application/json" \
  -d '{
    "event_key": "order.created",
    "recipient": {"email": "[email protected]", "name": "Dev User"},
    "payload": {"order_id": "O-1001"}
  }'

Expected response: 201 (event accepted) and a webhook run appears in Message Log.

Open Message Log now: trigger one event in Sample Project, then trace the webhook run from “created” to “delivered”.

“It never arrived” checklist

Wrong endpoint URL / DNS / TLS

Confirm the exact webhook URL, DNS resolution, and TLS certificate chain. Redirects (301/302) and invalid certs often look like delivery failures.

Signature verification rejecting (401)

If delivery reached your app but gets 401, re-check timestamp tolerance, exact signed string format, and raw-body handling. Verify with hash_equals and never compare signatures with ==.

Network / firewall blocks

Check inbound firewall rules, WAF behavior, reverse proxy allowlists, and container/network policies between edge and app.

Common gotcha: Teams rotate endpoint domains but forget to update webhook destinations. Message Log shows repeated failures, while application logs stay empty because traffic never reaches the service.

“Message Log shows 2xx but nothing happened”

When delivery logs show 200 but my app did not process webhook, the acknowledgment path and processing path are out of sync.

You returned 2xx before persisting (lost payload)

Persist webhook payloads before returning success:

1
2
3
4
5
6
DB::table('webhook_inbox')->updateOrInsert(
    ['dedupe_key' => $dedupeKey],
    ['payload' => json_decode($raw, true), 'received_at' => now(), 'updated_at' => now(), 'created_at' => now()]
);

return response()->json(['accepted' => true], 200);

Async job failed silently (no DLQ)

If workers fail after 2xx, you need durable inbox + dead-letter recovery. Implement Build a DLQ so “2xx but nothing happened” can’t happen.

Dedupe bug (ignored event)

Overly broad dedupe logic can drop legitimate events as duplicates. Ensure dedupe keys map to true event identity.

Mini incident: A team acknowledged webhooks and queued jobs, but dedupe used only event_key (not payload identity). New orders with the same event type were dropped; switching to a stable content-based dedupe key fixed it.

Interpreting common status patterns

Timeouts

If your webhook delivery log shows timeout what to check: worker saturation, DB lock waits, slow downstream APIs, and long synchronous code paths before response.

429 rate limiting

To debug 429 rate limit responses in webhook delivery logs, inspect rate-limit policy scope (IP, token, route), then smooth traffic via queues and bounded concurrency.

5xx dependency failures

5xx usually indicates your app or a dependency failed while processing. Correlate delivery timestamps with dependency health and error spikes.

4xx contract failures (bad request)

Persistent 4xx often means schema drift, auth/signature mismatch, or missing required fields. Use Common API errors and meanings (429, 401, 409) for fast triage.

Pattern in Message LogTypical causeFirst action
Repeated timeoutSlow request path or blocked dependencyReturn 2xx faster and queue heavy work
Burst of 429Receiver throttling too aggressivelyAdd buffering/backoff and tune limits
Repeated 5xxApp or dependency instabilityInspect app errors and dependency health
Repeated 401/400Signature or payload contract issueValidate raw-body signature and schema

Common failure modes

  1. Endpoint returns 301/302 (redirect) -> treated as non-2xx by many senders; retries.
  2. TLS/cert errors (self-signed, expired) -> connection failures; retries.
  3. Timeouts (slow code path) -> multiple attempts; duplicates.
  4. 429 rate limits -> retries amplify; need queue smoothing.
  5. Signature verification rejects due to raw-body mismatch or clock skew.
  6. “2xx but nothing happened” because you ack before persisting + job fails later.
  7. Idempotency bug discards real events as duplicates.

Minimum observability you should add

Correlation IDs (dedupe key)

1
2
3
4
5
6
7
8
$raw = $request->getContent();
$dedupeKey = hash('sha256', $raw);

// Log without secrets
logger()->info('sendpromptly_webhook_received', [
    'dedupe_key' => $dedupeKey,
    'received_at' => now()->toIso8601String(),
]);

Log attempt, latency, signature result

Store attempt number, delivery latency, and signature pass/fail result so Message Log entries can be correlated to app behavior in seconds.

Safe payload logging

Only log non-sensitive fields needed for debugging. Redact secrets, tokens, and personal data by default.

Micro checklist:

  • Include dedupe_key in every webhook log line.
  • Record attempt, status_code, latency_ms, and signature_valid.
  • Keep enough payload context to debug contract issues, but redact sensitive fields.
  • Alert when timeout/429/401 patterns spike across attempts.

Delivery log debugging summary

  • Debug in order: ingestion 201 -> Message Log run -> endpoint response details.
  • Most “never arrived” reports are URL/TLS/routing or signature validation issues.
  • Most “2xx but nothing happened” incidents come from ack-before-persist, silent async failures, or weak dedupe rules.
  • Status code patterns (timeouts, 429, 5xx, 4xx) point to distinct fixes; treat them differently.
  • Correlation IDs and safe structured logs make webhook failures reproducible and fast to resolve.

Make debugging deterministic: Store every webhook in an inbox table + DLQ, then verify in Message Log that every attempt ends in 2xx.