# Events

Source: https://solidhooks.com/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](https://solidhooks.com/docs/sources) 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

| Status                 | Meaning                                                                            |
| ---------------------- | ---------------------------------------------------------------------------------- |
| `received`             | Matched at least one connection and was dispatched.                                |
| `filtered`             | Connections exist, but their event-type filters excluded it.                       |
| `unmatched`            | The source has no active connections.                                              |
| `rejected`             | Failed signature verification. Carries a `rejectionReason` and is never delivered. |
| `duplicate`, `pending` | Legacy 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:

| Reason                       | Meaning                                                     |
| ---------------------------- | ----------------------------------------------------------- |
| `signature_mismatch`         | The signature did not match.                                |
| `missing_signature_header`   | The signature header was absent.                            |
| `missing_id_header`          | Standard Webhooks only: the `webhook-id` header was absent. |
| `missing_timestamp_header`   | The timestamp header was absent.                            |
| `malformed_signature_header` | The signature header could not be parsed.                   |
| `malformed_timestamp`        | The timestamp could not be parsed.                          |
| `timestamp_out_of_tolerance` | The timestamp fell outside the replay window.               |
| `verifier_misconfigured`     | The source's verifier is misconfigured.                     |
| `unsupported_verifier`       | The 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](https://solidhooks.com/docs/idempotency).

## List and filter events

**API**

```bash
curl -s "$API/v1/events?status=rejected&limit=20" \
  -H "authorization: Bearer $SDHK_KEY"
```

| Parameter           | Meaning                                                                         |
| ------------------- | ------------------------------------------------------------------------------- |
| `sourceId`          | Only events from this source.                                                   |
| `status`            | One of `pending`, `received`, `filtered`, `duplicate`, `rejected`, `unmatched`. |
| `from`, `to`        | Events created on or after `from` and on or before `to`.                        |
| `limit`             | Page size, 1 to 100. Defaults to 20.                                            |
| `cursor`            | Cursor from the previous page.                                                  |
| `search`            | Search term, up to 255 characters.                                              |
| `sortBy`, `sortDir` | Sort 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.

**Dashboard**

1. Open **Events**.
2. Filter by **Source**, **Status** and the date range.
3. Select a row to open the event panel with its headers, body, rejection reason and deliveries.

## 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](https://solidhooks.com/docs/replay).

## Live stream

`GET /v1/events/stream` tails new events as Server-Sent Events, so you can react without polling. See [Streams](https://solidhooks.com/docs/streams).

## Next steps

* [Sources](https://solidhooks.com/docs/sources) — where events come from
* [Deliveries](https://solidhooks.com/docs/deliveries) — what happens after an event is stored
* [Idempotency](https://solidhooks.com/docs/idempotency) — how duplicates are detected
* [Replay](https://solidhooks.com/docs/replay) — send an event again
* [Streams](https://solidhooks.com/docs/streams) — tail events live