Skip to content

Webhook sink: auth, signature, resumable delivery, reorg notifications, batching - #924

Draft
sduchesneau wants to merge 5 commits into
developfrom
feature/webhook-sink-delivery
Draft

Webhook sink: auth, signature, resumable delivery, reorg notifications, batching#924
sduchesneau wants to merge 5 commits into
developfrom
feature/webhook-sink-delivery

Conversation

@sduchesneau

@sduchesneau sduchesneau commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Five independent commits to substreams sink webhook, in review order:

  1. Auth header and HMAC signature. --webhook-auth-header-name / --webhook-auth-header-value-envvar send a verbatim header; --webhook-signing-secret-envvar adds X-Substreams-Signature: t=<unix>,v1=<hex> over <t>.<body>. Secrets come from env vars so they never sit on the command line. Client.Call now returns a *DeliveryError with status and attempt count once retries run out.
  2. Resumable delivery. --webhook-on-failure=exit writes the failed payload to <state-file>.pending, writes a JSON reason to --webhook-termination-log when that file exists, and exits with status 75. On the next start the pending payload is delivered before any Substreams stream is opened, so retrying against a dead endpoint costs no egress. The file is only written on failure: a kill mid-call leaves nothing behind, the cursor was not advanced, and the stream re-sends that block. A changed URL or secret resets first_attempt_at. Also exposes substreams_sink_progress_block.
  3. Sink library fix. With --undo-buffer-size, an undo signal reaching below the buffer while nothing had been emitted yet was swallowed. That is what a restart from a cursor on a fork produces, so the previous run's forked blocks were never rolled back. The buffer now reports whether it absorbed the undo and the sinker forwards it to the handler otherwise. Affects every buffered sink, not just the webhook one.
  4. Reorg notifications. --webhook-undo-url receives {"lastValidBlock": {"number", "id"}, "manifest": {"moduleName"}} per undo signal, with the same headers, retry, on-failure and pending-file rules as blocks. Opt-in; without it the cursor moves back and only replacement blocks are delivered, as before.
  5. Batching. --webhook-batch-max-blocks=N switches every call to {"manifest": {...}, "blocks": [{"clock", "data"}, ...]} with up to N blocks. A batch is sent when full, when --webhook-batch-max-wait (1s) has passed and the next block arrives, when the chain is live, before an undo notification, and when the stream ends. A failed batch is kept and resumed as one payload. Toggling batching while the sink is stopped discards a pending payload of the other shape and lets the stream re-send its blocks. Off by default.

Not included: a receive-window cap on the Substreams client.

Design context: streamingfast/services-control-plane#61.

A receiver had no way to tell the sink's POST from anyone else's. The header value and the signing secret come from environment variables so they never appear on the command line.
@dfuse-bot

Copy link
Copy Markdown

🔍 Vulnerabilities of ghcr.io/streamingfast/substreams:8cea0c5

📦 Image Reference ghcr.io/streamingfast/substreams:8cea0c5
digestsha256:579a47aa4abf8fef160c7028685e658b863818ae3c679aeda19aa2ab861e4282
vulnerabilitiescritical: 0 high: 0 medium: 0 low: 0
platformlinux/amd64
size124 MB
packages380
📦 Base Image ubuntu:24.04
also known as
  • c1ca75be10a22ea09ff0b7bbe8b82ee03553a4f9b795030ee2ec921e42418fc8
  • noble
  • noble-20260810
digestsha256:1e0a86e57d247923571b75e0aaf48a1449cf8c543d51fb3e07a4a7d7bfa79316
vulnerabilitiescritical: 0 high: 0 medium: 24 low: 11

Dropping a block after the retries run out is silent data loss, and retrying in-process holds a Substreams stream open for nothing. With --webhook-on-failure=exit the block waits on disk and is delivered before the next stream is opened, so a dead endpoint costs no egress while it is down.
With a buffer, an undo below the first buffered block was dropped when nothing had been emitted yet. That is the shape of a restart from a cursor on a fork: the previous run emitted the forked blocks, and the handler never heard they were gone.
Until now a receiver was sent blocks that later left the chain and never heard about it. The notification is opt-in because it changes what a receiver must handle, and goes to its own URL so the block endpoint keeps a single payload shape.
@sduchesneau
sduchesneau force-pushed the feature/webhook-sink-delivery branch from dc3fe0b to 24f3d70 Compare September 4, 2026 13:55
One call per block caps throughput at the receiver's round trip. With --webhook-batch-max-blocks the sink sends up to N blocks per call in a batch shape, opt-in so existing receivers keep the single-block shape.
@sduchesneau sduchesneau changed the title Webhook sink: auth, signature, resumable delivery, reorg notifications Webhook sink: auth, signature, resumable delivery, reorg notifications, batching Sep 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants