All docs
3 min read

Delivery log

Every dispatch — success or failure, first try or seventh — is recorded. The log is your source of truth when something didn't show up downstream.

What we store per attempt

Field Notes
id ULID for the delivery attempt.
webhook_id Which webhook ran.
submission_id Which submission triggered it.
event submission.created or submission.flagged.
attempt 1–7 for normal flow; resets per replay.
http_status The status code we got back, or null if no response.
response_body First 4 KB of the response, UTF-8. Anything larger is truncated.
response_headers A small subset (Content-Type, Server, Retry-After).
error Short error code if the request never completed (timeout, dns_error, tls_error, connection_refused).
latency_ms Time from request start to response end.
dispatched_at When we fired this attempt.
next_retry_at When we'll try again, or null if we're done.

We do not store the request body in the delivery log — that's already on the submission. The log focuses on what happened on the wire.

Retention

Plan Retention
Free 7 days
Pro 30 days
Team 90 days

After retention, the delivery rows vacate. Replays are still possible — replay reads from the submission, not the log — but you lose the historical attempt detail.

Inspect from the dashboard

Form → Webhooks → click a webhook → Deliveries.

You see a reverse-chronological list. Each row shows status, attempt count, latency, and a quick excerpt of the response body. Click in for the full detail view: full response, full headers, raw payload that was sent, and a Replay button.

The list filters by:

  • Outcome (success, retrying, failed)
  • Event type
  • Date range
  • Submission ID

Inspect via the API

GET /api/v1/webhooks/{webhook_id}/deliveries
Authorization: Bearer <token>

Returns a paginated list of deliveries. Add ?status=failed to see only failures, or ?submission_id=... to drill into one submission.

GET /api/v1/deliveries/{delivery_id}

Returns the full record including the truncated response body and the request payload that was sent.

Inspect via MCP

If you've connected Formspring's MCP server to your editor or assistant:

formspring.list_deliveries(webhook_id, status="failed", limit=20)
formspring.get_delivery(delivery_id)
formspring.replay_delivery(delivery_id)

Useful for debugging from inside a chat session — pull the failing response, eyeball it, fix the handler, replay.

What good looks like

A healthy webhook log is mostly attempt 1, mostly 200, latency under 500ms. Patterns to watch:

  • Climbing latency: your handler is doing too much synchronously. Move work to a queue and respond fast.
  • Repeated 5xx: downstream is broken. Check the response body in the log — we keep it for a reason.
  • Sporadic timeouts: cold starts. Warm the function, raise the timeout, or accept the retry will paper over it.
  • tls_error: cert expired or chain broken. Hit your URL with curl -v.

What's next