Skip to content

Add 1,625 doc-sourced sample payloads across 102 providers - #25

Open
garethx wants to merge 5 commits into
hookdeck:mainfrom
garethx:doc-sourced-samples
Open

Add 1,625 doc-sourced sample payloads across 102 providers#25
garethx wants to merge 5 commits into
hookdeck:mainfrom
garethx:doc-sourced-samples

Conversation

@garethx

@garethx garethx commented Aug 5, 2026

Copy link
Copy Markdown

Adds 1,625 sample payloads across 102 providers — 91 this repo has no entry
for at all, and the rest filling in never-captured topics for providers it
already carries. Nothing captured is overwritten.

Updated after @leggetter's review:
shopify's 29 files and scrapfly's 7 removed (both have capture harnesses), and
six providers moved out of latest/ into the version their docs were served at.
Details in the version section below.

They are not captured requests. Each is the example the vendor publishes in their
own documentation, harvested and reviewed in
hookdeck/webhook-registry
(samples-doc/), and each says so on its face:

{
  "headers": { "content-type": "application/json" },
  "body": { "...": "the vendor's documented example" },
  "topic": "payment.succeeded",
  "source": {
    "type": "vendor-documentation",
    "url": "https://vendor.example/docs/webhooks",
    "retrieved": "2026-07-29"
  }
}

Existing entries carry no source key, so absence of the key is what marks a
sample as observed
. That is what keeps this additive rather than a downgrade.

Why

The Example Webhooks picker covers 19 providers. Hookdeck supports 162. For the
rest there is nothing to mock against, and for most there is no captured traffic
to draw on yet either. A vendor's published example is a worse sample than a real
request and a much better one than an empty picker.

A capture always beats a doc example

Enforced before the PR was cut, not left to review: the exporter reads this repo
and will not emit a topic already captured here at that version. 248 doc samples
were dropped
on that rule — shopify 174, bigcommerce 32, paddlebilling 16,
github 10, adyen 6, gitlab 5, checkout 5. When real traffic for any remaining
provider+topic pair is sampled, it replaces the file rather than sitting beside it.

What's in it

Providers added 91
Samples added 1,625
Existing samples changed 0
Existing files changed 2, each explained below
yarn compile passes — 112 providers, 0 topic collisions

For the 11 providers already here, the doc samples go in that provider's own
version directory
, not a new latest/:

Provider Directory Samples added
adyen 1/ 94
bigcommerce 2025-04/ 3
checkout 1.0.25/ 115
github 2022-11-28/ 47
gitlab current/ 6
monday current/ 2
paddlebilling current/ 40
paypal 1.0/ 1
pipedrive 2/ 48

Six more move out of latest/ into the version the vendor stamps into the
payload: bridge-xyz2024-01-01, chargebeev2, docusignv2.1,
klaviyo2025-07-15, microsoft-graphv1.0, smartcar4.0.

The sweep behind that found "has a version field" is too broad a rule — most
providers have one and most don't qualify. Object versions (square, courier,
github's package_version), event-schema versions (checkout's 1.0.0/1.2.0/2.0.0,
zendesk, twitch's subscription.version) and asset versions (cloudinary's unix
timestamp) version the thing carried, not the contract carrying it. The test that
holds is whether the field versions the webhook API itself: api_version,
apiVersion, or a field the vendor documents as such — Smartcar's meta.version
is "Webhook API version" in their docs.

Two are deliberately left in latest/: huggingface's bare version: 3 has no
doc statement of what it versions, and notion's api_version appears on 4 of
23 samples.

shopify and scrapfly are no longer here at all. Both have capture
harnesses in this repo, and a doc example standing where a real one can be had
makes the gap look filled. Shopify's 29 were also unplaceable: read from
shopify.dev/docs/api/webhooks/latest, which serves current stable, while the
directory is 2024-10 — inaccessible since 2025-10-16, and 15 of the 29 topics
postdate it. Scrapfly's 7 are also incomplete: Scrapfly documents 8 crawler
events and crawler_url_visited was missing, which is its own argument for
capturing them.

The three existing files that change

Each was checked against the vendor's live documentation, not inferred from the
payloads on disk.

providers/monday/index.jsontopic_identifier typeevent.type

monday's payload is {"event": {"type": "create_pulse", ...}}; there is no
top-level type.
developer.monday.com:
"Every webhook sent to your endpoint will have an event field containing the
payload with the event's data." All four captured samples already in this repo
agree. The current value cannot resolve — requestReceiver.ts would have written
untitled-<md5> for every one of them, which is presumably why they were entered
by hand.

providers/pipedrive/index.jsonlatest_version 12, doc samples in a new 2/

The 12 captured samples are Webhooks v1: top-level event, plus v,
matches_filters and retry. Pipedrive removed the event field in v2 and
replaced it with meta.action + meta.entity
(migration guide),
and v2 has been the default since 2025-03-17.
So these are a different format, not a different rendering of the same one, and
they get their own version rather than merging into 1/.

topic_identifier stays event: it resolves for the v1 captures, meta.action
alone would yield create rather than create.deal, and only one identifier per
provider is expressible.

providers/scrapfly/README.md — reverted, no longer changed

Kept for the record, since the finding stands even though the files are gone.

X-Scrapfly-Webhook-Resource-Type names the product, not the event.
Scrapfly's crawler webhook docs
call it "Resource type (always crawler for crawler webhooks)" — so on this
family it is a constant, and keying on it would collapse all seven crawler events
into one topic called crawler. The event name is in
X-Scrapfly-Crawl-Event-Name. topic_identifier is left alone because the
existing value is what resolves for the Scrape and Extraction captures; the
crawler files carry their topic explicitly.

This is the one place where this PR's topic_identifier and the registry's
disagree, and it is a real disagreement rather than an oversight. The registry
now records X-Scrapfly-Crawl-Event-Name as scrapfly's discriminator with
join_check: verified (example crawler_cancelled, resolved from these very
samples). It is right for classifying deliveries; the value here is right for
resolving a topic out of the two captures this repo already holds. One
topic_identifier per provider cannot express both, and changing it would
retopic the existing scrape.json and extraction.json to crawler.

GitLab topics use the header vocabulary

GitLab's samples are named Job Hook.json, not build.json — the
X-Gitlab-Event header value, which is what topic_identifier points at and
what the seven captured samples here already use. The source corpus had named
them after the object_kind body field, which also meant the synthesized header
carried a string GitLab never sends. Renamed from
GitLab's own table,
which is also why it's a table and not a transform: build maps to Job Hook.

Contributing-guide conformance

  • one directory per provider under providers/

  • index.json with a display-name label, configs.latest_version, and
    configs.topic_identifier where the provider has one

  • latest_version is "latest" only for providers with no version scheme in
    their docs, which the guide's "if the provider doesn't offer versioning then
    input latest" covers

  • topic_identifier omitted where the provider has none — recharge has no
    event-type field in the payload, and adyen keeps this repo's existing empty
    value because its eventCode is nested inside
    notificationItems[].NotificationRequestItem rather than at the top level
    (Adyen webhook structure)

  • one <topic>.json per topic holding headers and body, filename derived from
    the topic the same way requestReceiver.ts does it (store/order/created
    store.order.created.json), with the unmodified topic inside the file

  • header-carried topic_identifiers lowercased, matching x-shopify-topic /
    x-github-event and the header keys in the samples themselves

    This one is no longer just a convention match. The casing mismatch it came from
    (Twitch documents Twitch-Eventsub-Subscription-Type and sends
    twitch-eventsub-subscription-type) turned out to be the visible end of a
    broader defect, now fixed in
    hookdeck/webhook-registry#4:
    header names are matched case-insensitively per RFC 9110 §5.1, and the registry
    publishes a case-folded discriminator_path_lookup beside the documented path
    for exactly this reason.

    Every header-carried topic_identifier in this PR was cross-checked against it.
    10 providers here have a header discriminator — bitbucket, commercelayer, github,
    gitlab, scrapfly, shopify, solidgate, twitch, woocommerce, zerohash — and 9 match
    exactly. The tenth is scrapfly, deliberately, for the reason below.

Headers are synthesized

Vendor docs publish bodies, not deliveries. Each file carries content-type plus
the topic header where the provider puts the event type in one. Signature headers
are absent — mock sends can't produce valid signatures anyway. A capture upgrades
the headers along with the body.

One thing a maintainer should decide

21 providers have a topic_identifier that a flat lookup won't resolve.

requestReceiver.ts resolves the identifier as
headers[ti] || body[ti] — one level, no paths. Every entry in this repo today
is flat, but 21 of these providers genuinely put the event type deeper: 15 dotted
(event.type, data.event_type, metadata.topic) and 6 inside an array
(events[].type, value[].changeType, entry[].changes[].field). A handful more
compose the topic from two fields — pipedrive's meta.action + meta.entity,
xero's eventCategory + eventType.

Nothing here breaks: compile.ts passes topic_identifier through untouched and
every file carries an explicit topic. But if Console's extraction is flat like
the receiver's, those identifiers are decorative. Tell me which it is and I'll
either leave them (accurate, and useful the day paths are supported) or drop them
to null (honest about what today's readers can do). Flattening them to the last
segment is the one option I'd avoid — event.typetype resolves to nothing
for monday and silently mis-resolves for others.

Covers 103 providers: 92 this repo has no entry for at all, and 11 it
already carries, where these fill in topics that have never been
captured. Nothing captured is overwritten — every file here is a topic
with no existing sample at that version.

None of them are captured requests. Each is the example the vendor
publishes in their own documentation, and each says so:

  "source": {
    "type": "vendor-documentation",
    "url":  "<the doc page the example was read from>",
    "retrieved": "2026-07-29"
  }

Existing entries have no `source` key, so its absence is what marks a
sample as observed. A capture must always beat a doc example: when real
traffic for one of these provider+topic pairs is sampled, it replaces
the file rather than sitting beside it. 248 doc samples were dropped for
exactly this reason before the PR was cut — shopify alone had 174 topics
already captured here.

Doc samples for an existing provider go in that provider's own version
directory, not a new `latest/`: shopify's in 2024-10/, github's in
2022-11-28/, checkout's in 1.0.25/, paypal's in 1.0/.

Three existing files change, each for a reason checked against the
vendor's own documentation:

- providers/monday/index.json — topic_identifier `type` -> `event.type`.
  monday's payload is {"event": {"type": ...}} with no top-level `type`.
  Confirmed by developer.monday.com ("Every webhook sent to your
  endpoint will have an `event` field containing the payload with the
  event's data") and by all four captured samples already here.
- providers/pipedrive/index.json — latest_version `1` -> `2`, and the
  doc samples land in a new `2/`. The 12 captured samples are Webhooks
  v1, which carries a top-level `event` field. Pipedrive removed that
  field in v2 and replaced it with meta.action + meta.entity; v2 has
  been the default since 2025-03-17. topic_identifier stays `event`
  because that is what resolves for the v1 captures, and one identifier
  has to serve both.
- providers/scrapfly/README.md — documents the Crawler API as a fourth
  webhook family whose event name is in X-Scrapfly-Crawl-Event-Name,
  since X-Scrapfly-Webhook-Resource-Type is "always `crawler`" there and
  would collapse all seven crawler events into one topic.

GitLab's samples are named for the X-Gitlab-Event header values
("Job Hook") rather than the object_kind body field ("build"), matching
the seven already captured here and GitLab's own published table.

Follows the contributing guide: display-name `label`,
`configs.latest_version`, `configs.topic_identifier` where the provider
has one, and one `<topic>.json` per topic holding `headers` and `body`.
`yarn compile` produces all 111 providers with no topic collisions.

Source: hookdeck/webhook-registry `samples-doc/`, which records the
vendor URL and retrieval date behind every file.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Aug 5, 2026

Copy link
Copy Markdown

@garethx is attempting to deploy a commit to the Hookdeck Team on Vercel.

A member of the Team first needs to authorize it.

Both found by regenerating the source tree from scratch and diffing it
against what was committed — the export is additive and never overwrites,
so a file can go stale without anything noticing.

- cloudinary: 1 sample (`upload`). The provider gained a webhook skill
  after the tree was last exported, so it had never been picked up.
- coinbase-commerce/charge.confirmed: `source.url` said
  `api-arcitecture`, which 404s. The registry corrected that typo in its
  own evidence layer and this copy was missed. A citation nobody can open
  is the one thing a `source` key must never be.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@garethx garethx changed the title Add 1,660 doc-sourced sample payloads across 103 providers Add 1,661 doc-sourced sample payloads across 104 providers Aug 5, 2026
@vercel

vercel Bot commented Aug 5, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
webhook-samples Ready Ready Preview Aug 5, 2026 2:14pm

Request Review

@leggetter
leggetter requested a lite review from Copilot August 5, 2026 14:14

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot wasn't able to review this pull request because it exceeds the maximum number of files (300). Try reducing the number of changed files and requesting a review from Copilot again.

@leggetter

Copy link
Copy Markdown
Collaborator

@garethx - Phil asked me to review this one. Most of it looks right: the 93 new providers, the source marking convention, and the monday/pipedrive/gitlab corrections. Three things need fixing first, and they're all the same shape - version handling.

1. Shopify - remove the 29 files

providers/shopify/index.json says latest_version: 2024-10. Shopify's latest stable is 2026-07, and 2024-10 stopped being accessible on 2025-10-16.

The files declare their source as shopify.dev/docs/api/webhooks/latest, which serves 2026-07 today. Requesting ?api[version]=2024-10 falls forward to 2026-07 because the 2024-10 docs are gone, so 2024-10 examples couldn't have been sourced at all. 15 of the 29 are inventory_transfers/* and inventory_shipments/*, which went to unstable in March 2025 targeting 2025-07 stable - they can't exist in 2024-10 either.

The captured samples in that directory all carry x-shopify-api-version: 2024-10; the new ones carry no version header at all. So the compiled 2024-10.json now publishes 181 samples that self-declare 2024-10 alongside 29 that are actually 2026-07.

The dedupe rule made this worse rather than better - it dropped the 174 Shopify doc samples that already had captures, and what survived was the set with no capture, which skews to topics that postdate 2024-10.

There's also scripts/shopify/2024-10/ - a trigger.js harness driving the Shopify CLI to fire real webhooks into requestReceiver.ts. Shopify is a provider where we shouldn't need doc examples at all. We'll do a separate pass to capture into 2026-07/ and bump latest_version.

2. Scrapfly - remove the 7 crawler files

scripts/scrapfly/ is a working capture harness, and providers/scrapfly/README.md opens by saying those files are real captures. Scrapfly's crawler webhooks attach via the same webhook_name parameter that triggerScrape and triggerExtraction already use, so a triggerCrawl() in capture.ts would get real ones.

The blocker you found is real - topic_identifier is x-scrapfly-webhook-resource-type, which is constant crawler, so the receiver would write every crawler event to one file. But leaving the config alone and hand-writing the payloads means these can never become captures, which cuts against the "a capture always beats a doc example" rule this PR adds to the README. We'd rather fix the receiver fallback and capture them properly as a follow-up.

Also, Scrapfly documents 8 crawler events - crawler_url_visited is missing.

3. latest_version

All three of these say latest, and the repo's rule is that latest is only for providers with no version scheme:

  • klaviyo2025-07-15 (the committed sample has meta.version: "2025-07-15")
  • chargebeev2 (api_version: "v2" in the sample)
  • microsoft-graphv1.0 (source URL is the v1.0 docs; Graph is v1.0/beta)

Worth sweeping the other 90 for the same thing. Where the vendor stamps a version into the payload, that's the directory name. discord and square both have a .version field, but those are object versions rather than API versions, so latest is correct there.

Rules that might help the agent

  • A doc example goes in the directory for the version the docs were served at. If the vendor only publishes "latest", it goes in the current latest version's directory, never an older one.
  • Before adding samples to a provider that already exists, check scripts/<provider>/. If there's a capture harness, use it or say why it wasn't used.
  • latest_version comes from the vendor's docs. If the payload carries a version field, that's the value.

One unrelated thing

doc_sourced: true never reaches consumers. compile.ts only reads label and configs.latest_version, so providers.json drops it. The per-sample source key does get through, so the sample-level contract holds. One line fixes the provider-level one.

On your open question about the dotted topic_identifiers - leave them as they are. They're accurate, they cost nothing while every file carries an explicit topic, and flattening to the last segment would mis-resolve.


Generated by Claude Code

All three findings from @leggetter's review, plus the sweep it asked for.

**shopify — 29 files removed.** They went in `2024-10/` because that is
the provider's latest_version, but they were read from
shopify.dev/docs/api/webhooks/latest, which serves the current stable
version. 2024-10 stopped being accessible on 2025-10-16, so they could
not have been sourced at it, and 15 of the 29 are inventory_transfers/*
and inventory_shipments/* topics that postdate it. The 181 captures in
that directory all carry `x-shopify-api-version: 2024-10`; these carried
no version header at all. `scripts/shopify/` can capture properly.

**scrapfly — 7 crawler files removed**, and providers/scrapfly/README.md
is back to its original text. `scripts/scrapfly/` can capture these once
requestReceiver.ts resolves an ordered topic_identifier list, which hookdeck#26
adds. Scrapfly documents 8 crawler events, not 7 — `crawler_url_visited`
was missing here, which is its own argument for capturing rather than
hand-writing them.

**Six providers move out of `latest/`:** bridge-xyz `2024-01-01`,
chargebee `v2`, docusign `v2.1`, klaviyo `2025-07-15`, microsoft-graph
`v1.0`, smartcar `4.0`.

The sweep across all 104 found that "has a version field" is too broad a
rule — most providers have one and most do not qualify. Object versions
(square, courier, github's `package_version`), event-schema versions
(checkout's 1.0.0/1.2.0/2.0.0, zendesk, twitch's
`subscription.version`) and asset versions (cloudinary's unix timestamp)
version the thing carried, not the contract carrying it. The test that
holds is whether the field versions the webhook API itself:
`api_version`, `apiVersion`, or a field the vendor documents as such —
Smartcar's `meta.version` is "Webhook API version" in their docs.

Two left in `latest/` deliberately: huggingface's bare `version: 3` has
no doc statement of what it versions, and notion's `api_version` appears
on 4 of 23 samples.

1,661 -> 1,625 samples across 102 providers. `yarn compile` passes with
112 providers, 0 topic collisions.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@garethx garethx changed the title Add 1,661 doc-sourced sample payloads across 104 providers Add 1,625 doc-sourced sample payloads across 102 providers Aug 6, 2026
@garethx

garethx commented Aug 6, 2026

Copy link
Copy Markdown
Author

Thanks — all three are fixed, and the sweep turned up a correction to the rule itself. Pushed as 20357af.

1. Shopify — removed

Confirmed everything you described before removing them. All 29 carry source.url: https://shopify.dev/docs/api/webhooks/latest, none carry an api-version header, and all 181 captures in that directory carry x-shopify-api-version: 2024-10. 15 of the 29 are inventory_shipments/* and inventory_transfers/*.

Your point about the dedupe making it worse is the one I'd missed. The rule dropped the 174 topics that already had captures, so what survived was precisely the set with no capture — which skews to topics postdating 2024-10. The filter that was supposed to protect ground truth selected for the least placeable examples.

2. Scrapfly — removed, README reverted

You're right that hand-writing them cuts against the rule this PR adds. They're also incomplete: Scrapfly documents 8 crawler events and crawler_url_visited was missing — which is its own argument for capturing rather than transcribing.

One thing to keep in view when #26's capture runs: those 7 samples are currently the provenance for scrapfly's resolved header discriminator in the registry (X-Scrapfly-Crawl-Event-Name, join_check: verified, from hookdeck/webhook-registry#4). Deleting them outright would orphan a recorded value, so they stay in the registry's samples-doc/ as evidence and are listed under not_for_upstream in its manifest. Real captures should replace them as the provenance.

3. latest_version — swept all 104

Applied your three, and the sweep found three more that qualify: bridge-xyz2024-01-01, docusignv2.1, smartcar4.0.

The sweep also says the rule needs narrowing. "Where the vendor stamps a version into the payload" matches 27 of the 104, and most of them shouldn't move — you spotted this with discord and square, but it's broader than those two:

Kind Examples Move?
API/webhook contract version api_version, apiVersion, smartcar's documented "Webhook API version" yes
Object version square data.object.version, courier audience_version, github package_version no
Event-schema version checkout 1.0.0/1.2.0/2.0.0 across events, zendesk zendesk_event_version, twitch subscription.version (1/2/beta) no
Asset version cloudinary version: 1690000000 — a unix timestamp no

The test that holds: does the field version the webhook API, or the thing the webhook is about? Checkout is the clearest case for why it matters — its samples carry three different version values across events, so there is no single directory the rule could even produce.

Two I left in latest/ rather than guess: huggingface's bare version: 3 has no doc statement of what it versions, and notion's api_version: 2025-09-03 appears on only 4 of its 23 samples.

The rest

doc_sourced and the dotted topic_identifiers — agreed on both, and #26 covers the compile.ts line.

All three of your rules are now written down in hookdeck/webhook-registry-checks so the next update doesn't rediscover them, along with the version-kind distinction and the dedupe interaction from #1.

leggetter added a commit that referenced this pull request Aug 6, 2026
`doc_sourced` was read from index.json but never published until this
branch. Nothing consumes it yet, so this is the cheapest moment to
change the shape — after #25 lands and stamps 104 providers it becomes
a migration.

Three problems with the boolean:

`config.doc_sourced === true` coerced absence to `false`, and `false`
is not "unknown" — it is a positive claim that the samples were
captured live. Every provider in the repo would have started asserting
that the moment the field was published, including ones nobody has
verified. `provenance` defaults to `unknown` instead, and an
unrecognised `sourced_via` fails the build rather than reaching
consumers looking authoritative.

Provenance is a property of a version, not a provider. `providers/
scrapfly` alone is mixed: the crawler samples were captured today, the
scrape and extraction samples in May, and `screenshot` can never be
captured at all because Hookdeck rejects Scrapfly's content-type. One
boolean on the provider cannot express that.

And docs are not equivalent to capture. Scrapfly's crawler docs state
the resource-type header is `crawler`; real deliveries send `crawl`.
Samples transcribed from those docs encode a value that does not exist
in production. Flattening both into one "verified" bit would hide
exactly the discrepancy a consumer needs to see.

`sourced_on` records when a version's samples were obtained, using the
oldest date where a version is mixed so it never overstates freshness.
Recorded for shopify/2026-07 and scrapfly/latest, both captured
first-hand. shopify 2023-01 and 2024-10 are left `unknown` rather than
inferred from commit dates.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019pce9oHWGjdwHsJNya1ovP
@leggetter

Copy link
Copy Markdown
Collaborator

We've built on doc_sourced in #26 — with a different shape

Your doc_sourced flag identified something this repo genuinely needed: a consumer downloading a sample has no way to tell whether it's a real delivery or a transcription. We've kept the idea and changed the shape in #26 (e5570d2), so flagging it here before this PR stamps 104 providers.

doc_sourced is now provenance, keyed by version:

{
  "label": "Shopify",
  "configs": { "latest_version": "2026-07", "topic_identifier": "x-shopify-topic" },
  "provenance": {
    "2026-07": { "sourced_via": "capture", "sourced_on": "2026-08-06" }
  }
}

sourced_via is capture | docs | unknown. Three reasons we moved off the boolean:

Absence was reading as a claim. compile.ts had config.doc_sourced === true, so any provider without the field published false — and false isn't "unknown", it's a positive assertion that the samples were captured live. Every existing provider would have started making that claim. provenance defaults to unknown, and an unrecognised sourced_via now fails the build rather than shipping a typo that looks authoritative.

It's a property of a version, not a provider. providers/scrapfly is mixed today: the crawler samples were captured this morning, scrape and extraction back in May, and screenshot can never be captured at all because Hookdeck rejects Scrapfly's content-type. Shopify spans 2023-01, 2024-10 and 2026-07. One boolean per provider can't describe any of that.

Docs and capture aren't equivalent, and this PR has a live example of why. The seven Scrapfly crawler payloads here were written from Scrapfly's docs, which state X-Scrapfly-Webhook-Resource-Type is crawler for crawler webhooks. We captured the real deliveries this morning — they send crawl. Anyone routing on the documented value matches nothing. That's not a criticism of the transcription; the docs are wrong. It's the argument for keeping the two distinguishable rather than collapsing them into one "verified" bit.

We also added sourced_on (YYYY-MM-DD). Provenance without a date only tells half the story: providers/shopify was pinned to latest_version: 2024-10 until this morning — captured, real, and pointing at a version Shopify stopped serving in October 2025. sourced_via: capture alone would have made it look trustworthy. Where a version's files span dates, use the oldest so the value never overstates freshness.

One note on capture: it describes how the request arrived, not how realistic the body is. The 217 Shopify samples we captured today came through a real delivery path with real headers, but the bodies are Shopify's synthetic fixtures and eleven are literally {}. That's documented in the README rather than split into more values.

Nothing here needs changing in this PR's payloads — it's the same idea with a provenance block instead of a boolean, and the providers you're adding would be { "sourced_via": "docs" } per version. Full rationale in README.md on #26.

leggetter added a commit that referenced this pull request Aug 6, 2026
…-6cqxsq

Enable the captures PR #25 substituted with doc examples
garethx and others added 2 commits August 6, 2026 17:58
hookdeck#26 replaced `doc_sourced` with a `provenance` block keyed by version.
93 index.json files move to it; no payload changes — the per-sample
`source` key was already the version-independent half of the same idea.

  "provenance": { "latest": { "sourced_via": "docs",
                              "sourced_on": "2026-07-29" } }

All 1,625 samples here carry `retrieved: 2026-07-29`, so that is the
`sourced_on` for every version, and the "oldest where files span dates"
rule has nothing to disambiguate yet.

The 11 providers that merge into an existing entry deliberately get no
`provenance`. After merging, those versions hold captures and doc
examples together, and `sourced_via` has no value for "both" — so no
claim is the honest record, and it reads as `unknown` rather than as a
false `capture`.

Merged main, which brings hookdeck#26's captures. Re-checked the additive rule
against it: 1,625 doc-sourced files across 102 providers, 0 of them
sitting where main now has a capture. shopify and scrapfly were already
removed from this branch in the previous commit, which is what hookdeck#26 then
captured for real — including `crawler_url_visited`, the eighth crawler
event this PR's hand-written set was missing.

`yarn compile` passes under hookdeck#26's stricter compile.ts, which now
validates `sourced_via` and fails an unrecognised value.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
garethx pushed a commit to garethx/webhook-samples that referenced this pull request Aug 6, 2026
Reviewing hookdeck#25 surfaced two providers where doc-sourced samples were
added despite this repo already having capture automation for them, and
one publishing gap. This is the groundwork so those captures can be run
instead.

Scrapfly Crawler API:
  Crawler deliveries set X-Scrapfly-Webhook-Resource-Type to a constant
  `crawler` and carry the event name in X-Scrapfly-Crawl-Event-Name, so
  the receiver wrote every crawler event to one crawler.json. That is
  what made the events look uncapturable.

  topic_identifier now accepts an ordered list and requestReceiver takes
  the first key that resolves, so crawler deliveries resolve by event
  name while scrape and extraction still fall through to resource-type.
  capture.ts gains a /crawl trigger covering the four events a clean
  crawl emits; the four that only fire on a failed or interrupted crawl
  are excluded from the delete-then-wait cycle so a normal run can't
  destroy a sample it cannot recreate.

Shopify 2026-07:
  providers/shopify is pinned to 2024-10, which stopped being accessible
  on 2025-10-16. Adds scripts/shopify/2026-07 alongside the 2024-10
  harness: 217 topics extracted from the current docs (22 gone since
  2024-10, 31 new), version taken from the directory name, app path and
  base URL via env instead of editing the file, and the off-by-one that
  triggered an undefined topic on the last iteration fixed.

  latest_version stays 2024-10 until the capture has actually run --
  compile publishes latest_version and the version list separately, so
  pointing at an empty version would break consumers.

compile.ts:
  doc_sourced was read from index.json but never published, so consumers
  could not tell a doc-sourced provider from a captured one without
  downloading a version file. It now reaches providers.json.

Verified: yarn compile passes; receiver resolves crawler, scrape,
shopify header and monday body identifiers correctly, and no longer
throws on a null body.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XjdTHaFed88Xd6fBFbJTKf
@garethx

garethx commented Aug 6, 2026

Copy link
Copy Markdown
Author

Adopted provenance — pushed as 6818666, with main merged in.

Payloads unchanged, 93 index.json files updated. Each doc-sourced version now carries { "sourced_via": "docs", "sourced_on": "2026-07-29" }. All 1,625 samples share that retrieval date, so the oldest-wins rule has nothing to disambiguate yet. yarn compile passes under the stricter validation.

Your absence-was-reading-as-a-claim point is the one that generalises. It's the same defect the registry calls absence is data — a missing fact and a negative fact must not look alike — and it had crossed a repo boundary without anyone noticing. Worth it landing on the schema rather than in a comment.

The 11 providers that merge into an existing entry get no provenance. After merging, those versions hold captures and doc examples together, and sourced_via has no value for "both" — so no claim reads as unknown, which is true, rather than a docs that would overwrite a real capture's record or a capture that would launder a transcription. Flagging it as a gap in the shape rather than working around it: mixed-within-a-version is representable only as absence right now.

The Scrapfly crawl finding

That's the most valuable thing to come out of this review, and it's now recorded in the registry as discriminator.families[].contradicted_by_capture — hookdeck/webhook-registry#9. Both readings kept: the vendor's quote unchanged, the observation beside it, and which one a router should trust left to a person.

It's the first time live traffic rather than another doc read is what disagrees, which is the loop the registry's CAPTURE-QUEUE describes — and the argument for keeping the two distinguishable, made better than any of my reasoning did.

Scrapfly's remaining three

#26 captured 5 of the 7. Four matched doc examples in the registry's samples-doc/ and the capture-beats-doc rule dropped them automatically — the rule working rather than an exception to it.

Three are left: crawler_cancelled, crawler_stopped, crawler_url_failed. Per #26's description those only fire on a crawl that fails or is interrupted, which the harness deliberately doesn't provoke so a normal run can't destroy a sample it can't recreate.

That's a call for you, not me, so they're held back rather than added to this PR unasked. Is a doc example the right fallback for an event a harness structurally cannot reach — or would you rather the gap stay visible? If you want them, they're three files and I'll add them with sourced_via: docs.

One note either way: those three are currently the provenance for scrapfly's resolved header discriminator in the registry, so if they go, that value's evidence should move with them.

All of it is written up in SAMPLE-PUBLISHING.md in webhook-registry-checks (hookdeck/webhook-registry-checks#2) — eight rules now, with the crawl case as rule 1's evidence.

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.

3 participants