SolidHooksSolidHooks/ Docs

Events

What SolidHooks stores for every inbound request, the event statuses, and how to list, filter and replay events.

An event is a stored inbound webhook: the raw body, the request headers and the parsed payload. Events are immutable. Once written they do not change, so an event is the record of what arrived.

Event ids start with evt_. Each event belongs to the source that received it.

How it works

The pipeline is: provider, ingest URL, event stored, connections matched, deliveries created. The event is stored first. Whether it is then delivered depends on its status.

An event carries:

  • the raw body exactly as received, plus the parsed payload
  • the request headers and any query parameters on the ingest URL
  • the resolved eventType, read from the source's eventTypeField at ingest time
  • the idempotencyKey, if the source computes one
  • a status and, for rejected events, a rejectionReason
  • verifiedWith, the verifier that ran at ingest, kept so editing the source later never relabels history

If a body is not valid UTF-8, isBinary is true, raw is a lossy rendering, and rawBase64 holds the exact bytes.

Statuses

StatusMeaning
receivedMatched at least one connection and was dispatched.
filteredConnections exist, but their event-type filters excluded it.
unmatchedThe source has no active connections.
rejectedFailed signature verification. Carries a rejectionReason and is never delivered.
duplicate, pendingLegacy values. Ingest no longer writes either.

Rejected events

A request that fails verification gets a deliberately opaque 401 with the same message whatever the cause, so the response cannot be used to probe signatures. The request is still stored as a rejected event, so you can see what arrived and why it was turned away.

rejectionReason is one of:

ReasonMeaning
signature_mismatchThe signature did not match.
missing_signature_headerThe signature header was absent.
missing_id_headerStandard Webhooks only: the webhook-id header was absent.
missing_timestamp_headerThe timestamp header was absent.
malformed_signature_headerThe signature header could not be parsed.
malformed_timestampThe timestamp could not be parsed.
timestamp_out_of_toleranceThe timestamp fell outside the replay window.
verifier_misconfiguredThe source's verifier is misconfigured.
unsupported_verifierThe verifier type is not supported.

Rejected events are never deduplicated, never delivered, and cannot be replayed.

Rejected events are a sample

Rejected events are rate-limited per source. Under a flood of bad requests the log is a representative sample, not a complete ledger of every 401.

Deduplication

When a source has idempotencyKeys, a repeat of an earlier request returns {"ok":true,"duplicate":true} instead of storing a second event. Duplicate requests are never counted against your quota. See Idempotency.

List and filter events

curl -s "$API/v1/events?status=rejected&limit=20" \
  -H "authorization: Bearer $SDHK_KEY"
ParameterMeaning
sourceIdOnly events from this source.
statusOne of pending, received, filtered, duplicate, rejected, unmatched.
from, toEvents created on or after from and on or before to.
limitPage size, 1 to 100. Defaults to 20.
cursorCursor from the previous page.
searchSearch term, up to 255 characters.
sortBy, sortDirSort field, and asc or desc.

Related endpoints:

  • GET /v1/events/{id} returns one event with its deliveries.
  • GET /v1/events/{id}/timeline returns the event's whole journey, ingest phases then each delivery's attempts. Start here when a webhook seems to have gone missing.
  • GET /v1/events/has-any tells a new, empty project from an empty filter.

All of these need read access to events. Project-scoped endpoints take an optional X-Project-Id header.

Retention

Events are kept for your plan's retentionDays. Older events and their deliveries are purged nightly and can no longer be replayed.

Currently, we keep events for 3 days on Hobby, 7 on Launch, 30 on Scale and 90 on Enterprise. You can get your plan retention days by using GET /v1/billing/plan.

Replay

POST /v1/events/{id}/replay re-runs an event through its currently active connections. It creates new deliveries, which are billed. See Replay.

Live stream

GET /v1/events/stream tails new events as Server-Sent Events, so you can react without polling. See Streams.

Next steps