Document the webhook contract in the README

Chiusa
#97 2 commenti 0 reazioni 1 assegnatario Vedi su GitHub

I maintainer di solito rispondono entro 2 giorni

@aziemels-byte ci sta già lavorando.

Dal 20/10/2026.

  • #184 di @aziemels-byte — aperta

Valutazione

Difficoltà
3/5
Tempo stimato
1-2 giorni
Idoneità per principianti
70/100
Tipo di issue
Documentazione
Chiarezza
Specificata chiaramente
Stato di attività
Attiva
Stack tecnologico
nodejs, typescript

Direzione di ricerca

Start by reading the webhook source the issue names: backend/src/modules/webhooks/dispatcher.ts for the envelope and signature, and backend/src/modules/submissions/verdict.ts for when the events fire. Then replace the two-line note at README.md lines 230-231 with a ### Webhooks section, and check the receiver snippet against openssl dgst -sha256 -hmac. Done when each statement matches the cited file and line, the acceptance criteria are met, and git diff --stat shows only README.md.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

docs documentation good first issue medium Stellar Wave

Summary

The README lists webhook event names and a header name in two lines, and nothing else. A marketplace integrating with Verity cannot tell what the payload looks like, how to verify the signature, whether a failed delivery is retried, which URLs are accepted, or which verdicts send nothing. Write a short webhook reference that is accurate to the code.

Background

A marketplace (or any account that submits work) can register webhook URLs to be told when a submission's status changes (README "API"; docs/ARCHITECTURE.md, "Backend", modules/webhooks/). README "Known limits" already says "webhooks are delivered once with no retry". Because the payload is signed with a shared secret, receivers must verify it, and a wrong description in the README would lead integrators to trust or reject the wrong bytes.

Current behaviour

README.md lines 230-231 (the whole current documentation):

Webhooks (HMAC-SHA256 in `x-verity-signature`): `submission.verified`,
`submission.slashed`, `submission.disputed`.

Facts to document, each verified in the code (re-find line numbers by function name if they have moved):

  1. Registration: POST /v1/marketplaces/:id/webhooks, backend/src/modules/marketplaces/routes.ts line 26. Requires a bearer token. Body: url (string, valid URL), secret (string, at least 8 characters), events (non-empty array of strings). The caller must be the marketplace's own account, otherwise 403 { "error": "only the marketplace's own account can manage its webhooks" }. The URL is checked with assertPublicHttpUrl (line 40) and the row is returned with 201. Event names are not validated against a list: a subscription only receives events whose name is in its events array (dispatcher.ts line 14).
  2. Which marketplace: a submission belongs to the marketplace of the account that submitted it. POST /v1/submissions calls findOrCreateMarketplaceForAccount(agent) (backend/src/modules/submissions/routes.ts lines 158 and 56-68 of marketplaces/routes.ts), which creates a marketplace named after the account's address on first use. POST /v1/marketplaces (line 8) returns the existing marketplace for the caller if there is one, so a webhook is subscribed on behalf of the account that submits the work.
  3. Events and when they fire:
    • submission.verified and submission.slashed: recordTerminalVerdict in backend/src/modules/submissions/verdict.ts, lines 58-64, only on the call that actually moves the submission into that status (transitioned), so once per submission. Payload data: { submission_id, status, tx_hash } where submission_id is Verity's own UUID for the submission, status is "verified" or "slashed", and tx_hash is the finalize or resolve transaction hash, or null when the transition was observed by the reconcile pass.
    • submission.disputed: backend/src/workers/indexer.ts, case challenge_opened, dispatched only when the challenge row is newly inserted. data: { submission_id, status: "disputed", tx_hash } with the hash of the challenge transaction.
    • expired_unverified sends nothing: the condition on verdict.ts line 58 is terminal === "verified" || terminal === "slashed". A verdict of verified can be a "verified by default" window outcome; the event name does not say how it was reached (the submission's resolution_method, from GET /v1/submissions/:id, does).
  4. Request: backend/src/modules/webhooks/dispatcher.ts. Method POST, headers content-type: application/json and x-verity-signature, body JSON.stringify({ event: eventType, data: payload }) (line 15), so the envelope is { "event": "submission.verified", "data": { ... } }.
  5. Signature (line 16): createHmac("sha256", sub.secret).update(body).digest("hex"). It is the lowercase hex HMAC-SHA256 of the exact request body string, keyed with the secret given at registration. Verify against the raw body bytes, not a re-serialised object.
  6. Delivery: one attempt, no retry (attempts: 1 is written on both the success and the failure path). fetch uses redirect: "error" (line 27), so a redirect fails the delivery. A request that does not finish within 30 seconds is aborted by the global fetch timeout in backend/src/config/net.ts. A 2xx response is recorded delivered, anything else failed, in the webhook_delivery table; there is no API to read those rows. The webhook is dispatched inline during verdict handling and its failure is only logged.
  7. URL rule: backend/src/modules/webhooks/url-guard.ts, assertPublicHttpUrl. Only http: and https:; the host must resolve, and an address in a private, loopback or link-local range is refused with 400 (webhook host <host> resolves to a non-public address). The check runs at registration and again at send time. Describe it as "public addresses only" without listing every range.

Expected behaviour

The README has a webhook reference section, linked from the API section, that an integrator can implement a receiver from, including a signature check.

Proposed approach

  1. Read the source locations in the list above.
  2. In README.md replace the two-line webhook note (lines 230-231) with a subsection (suggested ### Webhooks) containing: how to register (request body, auth, who may register, which account's submissions it covers); a table of the three events with when each fires and the data fields; the example envelope {"event":"submission.verified","data":{"submission_id":"<uuid>","status":"verified","tx_hash":"<hash or null>"}} clearly marked as a shape example with placeholders (no fabricated real ids); the signature rule (fact 5) and a short Node receiver snippet using createHmac and timingSafeEqual; the delivery rules (fact 6); the URL rule (fact 7); and the sentence that expired_unverified sends no webhook.
  3. Test your signature snippet yourself: compute the expected signature for a sample body with both your snippet and openssl dgst -sha256 -hmac "<secret>" and confirm they match. Say in the PR how you checked.
  4. Keep the "Known limits" bullet about no retry as it is; make sure the new section agrees with it.
  5. Pitfalls: submission_id is the Verity UUID, not the on-chain numeric id; do not call the signature "base64"; do not claim retries, a delivery log API, or a delivery guarantee; do not imply that submission.verified always means consensus.

Files

  • README.md: replace the two-line webhook note with a reference section; no other edits.

Acceptance criteria

  • The three events are listed with their trigger and data fields, and it is stated that expired_unverified sends nothing.
  • The envelope { event, data: { submission_id, status, tx_hash } } is documented, with a note that submission_id is Verity's UUID and tx_hash can be null.
  • The signature is described as hex HMAC-SHA256 over the raw body with the registration secret, in the x-verity-signature header, with a receiver snippet you tested.
  • No retry, one attempt, no redirects, and the 30 second timeout are stated.
  • The public-address-only URL rule and the registration requirements (auth, own account, secret at least 8 characters, events non-empty) are stated.
  • The text says the event name does not say whether a verified verdict was reached by consensus or by the window elapsing.
  • No other part of the README changed.

How to verify

  • Re-read each statement against the file and line named above.
  • Run the snippet against a sample body and compare with printf '%s' '<body>' | openssl dgst -sha256 -hmac '<secret>'.
  • Optionally run pnpm dev, register a webhook with POST /v1/marketplaces/:id/webhooks pointing at a public request-catcher URL you control, and confirm the payload and header (needs a funded testnet account; skip if you cannot and say so).
  • git diff --stat shows only README.md.

Out of scope

  • Adding retries, a delivery log endpoint, new events, or changing the payload or signature scheme.
  • Documenting other endpoints or the attestation format (separate issues).
  • Changes to url-guard.ts or the dispatcher.

Notes

Difficulty: medium; the facts are listed, the work is accuracy and a tested snippet. Merge-conflict hotspot: three documentation issues edit README.md (this one, the attestation verification section and the API auth/error table). Edit only your own section and rebase if another lands first. Line numbers refer to the working tree when this issue was written.

Lingua principale
TypeScript
Stelle
0
Fork
1
Merge medio
1g 22h
PR unite (30g)
106

Preparare l'ambiente

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Issue simili

Altre issue su TypeScript

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.