Resubmit a stored event as a new undelivered event (closes #250)
All checks were successful
check / check (push) Successful in 3m1s
All checks were successful
check / check (push) Successful in 3m1s
This commit was merged in pull request #251.
This commit is contained in:
58
README.md
58
README.md
@@ -903,10 +903,13 @@ webhooker solves this by acting as a durable intermediary:
|
||||
targets simultaneously. This enables patterns like forwarding a
|
||||
GitHub webhook to both a deployment service and a Slack channel.
|
||||
|
||||
5. **Replay** (not yet implemented) — Every received event is stored in
|
||||
full, which is what manual redelivery for debugging or testing will
|
||||
be built on. No redelivery exists today, in the web UI or the API;
|
||||
see [TODO.md](TODO.md).
|
||||
5. **Replay and resubmit** — Every received event is stored in full,
|
||||
and the event log offers two redelivery actions built on that.
|
||||
**Replay** re-sends one finished delivery to its own target, for
|
||||
recovering a delivery that failed. **Resubmit** re-injects the stored
|
||||
event as a new undelivered event and fans it out to every currently
|
||||
active target, for firing captured traffic at a backend under
|
||||
development. Both are web UI actions; there is no API for either.
|
||||
|
||||
### Use Cases
|
||||
|
||||
@@ -915,8 +918,8 @@ webhooker solves this by acting as a durable intermediary:
|
||||
- **Observability** via Prometheus metrics on webhook frequency, payload
|
||||
size, and delivery performance
|
||||
- **Debugging** and introspection of webhook payloads in the web UI
|
||||
- **Replay** of webhook events for application testing and development
|
||||
(planned; not yet implemented)
|
||||
- **Resubmit** of captured webhook events for application testing and
|
||||
development, and **replay** of a single failed delivery for recovery
|
||||
- **Fan-out** delivery of a single webhook to multiple downstream
|
||||
targets
|
||||
- **High-availability ingestion** for delivery to less reliable backend
|
||||
@@ -1224,7 +1227,7 @@ A programmatic access credential for API authentication.
|
||||
#### Event
|
||||
|
||||
A captured incoming webhook request. Stores the complete HTTP request
|
||||
data for auditing and for the planned replay capability.
|
||||
data for auditing, for replay, and for resubmission.
|
||||
|
||||
| Field | Type | Description |
|
||||
| -------------- | ------ | ----------- |
|
||||
@@ -1235,6 +1238,7 @@ data for auditing and for the planned replay capability.
|
||||
| `headers` | JSON | Complete request headers |
|
||||
| `body` | text | Raw request body |
|
||||
| `content_type` | string | Content-Type header value |
|
||||
| `resubmitted_from_id` | UUID | The event this one was copied from by a resubmit (nullable; empty for an event that arrived on the receiver). Not a foreign key: the source event can be reaped by retention while its copies remain |
|
||||
|
||||
**Relations:** Belongs to Webhook. Belongs to Entrypoint. Has many
|
||||
Deliveries.
|
||||
@@ -1283,6 +1287,30 @@ message on the event log rather than delivering from stale
|
||||
configuration, and a replay is refused while an earlier one for the
|
||||
same event and target is still pending or retrying.
|
||||
|
||||
**Resubmit.** Replay recovers one delivery; **resubmit** re-injects one
|
||||
EVENT. The event log offers a per-event **Resubmit** action that stores
|
||||
a NEW event copying the stored one's `method`, `headers`, `body` and
|
||||
`content_type` verbatim, then fans it out to the webhook's currently
|
||||
**active** targets — resolved fresh by the same query the receiver
|
||||
uses, so a target created long after the original event arrived
|
||||
receives it. That is the difference that matters: a target added to
|
||||
test a backend under development has no prior delivery, so there is
|
||||
nothing to replay to it, while a resubmit reaches it like any other
|
||||
active target. Inactive targets are skipped, exactly as the receiver
|
||||
skips them.
|
||||
|
||||
The new event is a first-class event in the log with its own
|
||||
deliveries, not a marker on the one it came from, and the original's
|
||||
deliveries are left untouched. It records `resubmitted_from_id`, and
|
||||
the event log shows the relationship both ways, so a captured event
|
||||
fired twenty times at a backend stays traceable. Resubmitting the same
|
||||
event repeatedly is supported and is the point of the action — there is
|
||||
no in-flight refusal; the route's own rate limit is what bounds it.
|
||||
|
||||
Inbound signature verification is not re-run on a resubmit. There is no
|
||||
inbound signature to check on a copy the operator submits, and the
|
||||
route is authenticated and CSRF-protected as an operator action.
|
||||
|
||||
#### DeliveryResult
|
||||
|
||||
The result of a single delivery attempt. Every attempt (including
|
||||
@@ -1539,10 +1567,11 @@ unknown) one while one of its deliveries is still `retrying`, both
|
||||
recovery paths above terminally mark that delivery `failed` and record a
|
||||
`DeliveryResult` naming the current target type as the reason, logging it
|
||||
at warn level. The delivery is not re-dispatched under the new type — the
|
||||
operator never asked for that delivery — and while the event itself
|
||||
remains stored in the per-webhook event database, there is no way to
|
||||
redeliver it: manual redelivery is planned, not implemented (see
|
||||
[TODO.md](TODO.md)).
|
||||
operator never asked for that delivery — and the event itself remains
|
||||
stored in the per-webhook event database. Nothing is lost: the delivery
|
||||
is `failed`, which is terminal, so the event log offers **Replay** on
|
||||
it to re-send it to that target under the new type, and **Resubmit** on
|
||||
the event to re-inject it to every currently active target.
|
||||
|
||||
### Circuit Breaker (HTTP and Slack Targets with Retries)
|
||||
|
||||
@@ -1615,13 +1644,16 @@ arriving and being stored, they are just not getting anywhere.
|
||||
| `webhooker_deliveries_failed_total` | counter | Deliveries that failed terminally and will not be retried |
|
||||
| `webhooker_delivery_retries_total` | counter | Deliveries put back into `retrying` |
|
||||
| `webhooker_delivery_replays_total` | counter | Deliveries an operator replayed from the event log. A replay runs the ordinary engine path, so it also moves the attempt, outcome and duration series; this is the only one that separates it from ordinary traffic |
|
||||
| `webhooker_events_resubmitted_total` | counter | Stored events an operator re-injected from the event log. The new event also moves `webhooker_events_received_total`, since it is a stored event the delivery side is compared against; this counter is what separates the two. Unlabelled: the target types it fans out to belong to the delivery series |
|
||||
| `webhooker_delivery_duration_seconds` | histogram | Wall time of a single dispatched delivery attempt, the same duration the attempt's `DeliveryResult` records |
|
||||
| `webhooker_deliveries_pending` | gauge | Deliveries currently in `pending` |
|
||||
| `webhooker_deliveries_retrying` | gauge | Deliveries currently in `retrying` |
|
||||
| `webhooker_circuit_breakers_open` | gauge | Circuit breakers currently open |
|
||||
|
||||
Every delivery metric carries exactly one label, `target_type`, and
|
||||
cardinality is the whole reason for that restriction. A target type is
|
||||
cardinality is the whole reason for that restriction. The two
|
||||
event-level counters carry no label at all — an event is not the
|
||||
property of any one target type. A target type is
|
||||
one of four compile-time constants, so the label domain is bounded by
|
||||
construction; a value outside that set collapses to `unknown` rather
|
||||
than minting a series of its own. Target ids, event ids and entrypoint
|
||||
@@ -2213,6 +2245,7 @@ abuse limit later; they are tracked as future work.
|
||||
| `POST` | `/source/{id}/delete` | Delete webhook |
|
||||
| `GET` | `/source/{id}/logs` | Webhook event logs |
|
||||
| `POST` | `/source/{id}/deliveries/{deliveryID}/replay` | Replay a finished delivery: creates a new delivery for the same event against the target's current configuration (30 per minute per bucket, then `429`) |
|
||||
| `POST` | `/source/{id}/events/{eventID}/resubmit` | Resubmit a stored event: creates a new event copying it and fans that out to every currently active target (30 per minute per bucket, then `429`) |
|
||||
| `POST` | `/source/{id}/entrypoints` | Add entrypoint to webhook |
|
||||
| `POST` | `/source/{id}/entrypoints/{entrypointID}/delete` | Delete an entrypoint |
|
||||
| `POST` | `/source/{id}/entrypoints/{entrypointID}/toggle` | Enable or disable an entrypoint |
|
||||
@@ -2303,6 +2336,7 @@ webhooker/
|
||||
│ │ ├── handlers.go # Base handler struct, JSON helpers, template rendering
|
||||
│ │ ├── auth.go # Login, logout handlers
|
||||
│ │ ├── delivery_replay.go # Per-delivery replay: new delivery, current target config
|
||||
│ │ ├── event_resubmit.go # Event resubmit: new event, all currently active targets
|
||||
│ │ ├── entrypoint_view.go # Masked entrypoint view for templates
|
||||
│ │ ├── event_log_view.go # Event log projection, byte-capped in SQL
|
||||
│ │ ├── healthcheck.go # Health check handler
|
||||
|
||||
Reference in New Issue
Block a user