Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions listener/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -395,3 +395,43 @@ EXPIRATION_DEFAULT_MS=86400000
# Enable dry-run mode to parse and validate events without persisting or delivering notifications.
# Set to 'true' to enable dry-run mode (default: false).
# DRY_RUN=false

# -----------------------------------------------------------------------------
# High-Volume Event Batching
# -----------------------------------------------------------------------------

# Coalesce selected high-volume event groups into a single batched message
# instead of one delivery per event. Off by default; when disabled every event
# is still delivered immediately as an individual message.
# EVENT_BATCHING_ENABLED=false

# Maximum events accumulated per group before the batch is flushed early.
# Default: 50
# EVENT_BATCHING_MAX_SIZE=50

# Longest an event waits (ms) before its batch is flushed. Default: 5000
# EVENT_BATCHING_WINDOW_MS=5000

# -----------------------------------------------------------------------------
# Periodic Digest Summaries
# -----------------------------------------------------------------------------

# Send selected recipients periodic summaries of event activity instead of an
# immediate alert per event. Off by default. Every event in a digest stays
# traceable to its originating record.
# DIGEST_ENABLED=false

# Digest period (ms). Default: 3600000 (hourly). Use 86400000 for daily.
# DIGEST_INTERVAL_MS=3600000

# Maximum events accumulated for a recipient before an early digest. Default: 100
# DIGEST_MAX_EVENTS=100

# -----------------------------------------------------------------------------
# Localization (i18n)
# -----------------------------------------------------------------------------

# Initial locale for user-facing notification text. Must be a supported locale
# (en, es). Missing translations fall back to this locale, then to English.
# DEFAULT_LOCALE=en

86 changes: 86 additions & 0 deletions listener/docs/WEBHOOK_PAYLOAD_VERSIONING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# Outbound Webhook Payload Versioning

Every webhook this listener delivers carries an explicit, stable payload version
so an external consumer can identify the schema it is receiving without
introspecting the body heuristically.

- Body field: `schemaVersion` (e.g. `"1.0"`)
- Request header: `x-notifychain-payload-version` (e.g. `"1.0"`)

Both are emitted by the delivery layer
(`listener/src/services/providers/webhook-provider.ts`, via
`listener/src/utils/webhook-payload-version.ts`), so producers and consumers
agree on a single source of truth.

> Note: this is **distinct** from `version` in `notification-version.ts`, which
> versions the *internal* notification protocol stored in the database. The
> webhook contract is a separate, consumer-facing surface and therefore gets its
> own identifier.

---

## Example payload

```json
{
"schemaVersion": "1.0",
"eventId": "evt_123",
"contractAddress": "CXXX...",
"notificationType": "contract_event",
"message": "task_created",
"occurredAt": "2026-07-26T12:00:00.000Z"
}
```

Example request headers:

```
Content-Type: application/json
x-notifychain-payload-version: 1.0
```

---

## Compatibility rules

Versions use a `MAJOR.MINOR` scheme:

- **MAJOR** is bumped for any breaking change — a removed field, a rename, a
change of type, or a change of meaning for an existing field. A new major
version is **not** compatible with the previous one.
- **MINOR** is bumped for additive, backward-compatible changes (new optional
fields, new enum members). Consumers **MUST** ignore fields they do not
recognise, so a minor bump never breaks an existing consumer.

Consumers **MUST** reject a payload whose MAJOR version they do not support, and
**SHOULD** log-and-continue on an unknown MINOR version within a supported
MAJOR. Producers always emit the current version by default; pinning an older
supported version is allowed for a migration window.

The machine-readable registry lives in
`WEBHOOK_PAYLOAD_VERSIONS` (`listener/src/utils/webhook-payload-version.ts`).
Never mutate an existing entry's `requiredFields` in a way that removes a field —
that is a breaking change and requires a new MAJOR version.

---

## Version history

| Version | Status | Released | Required fields |
|---------|--------|------------|--------------------------|
| `1.0` | active | 2026-07-26 | `schemaVersion` |

---

## Consumer guidance

1. Read `x-notifychain-payload-version` (or the body's `schemaVersion`, which
always match).
2. Split on `.` and check the MAJOR component is one you support.
3. Ignore unknown fields — they are the mechanism by which MINOR versions stay
backward-compatible.

Regression protection: `listener/src/utils/webhook-payload-version.test.ts`
pins representative payloads across versions, and
`listener/src/services/providers/webhook-provider.test.ts` asserts that every
delivered payload is stamped before it leaves the process.
Loading
Loading