Skip to content

Enable the captures PR #25 substituted with doc examples - #26

Merged
leggetter merged 4 commits into
mainfrom
claude/webhook-samples-pr-review-6cqxsq
Aug 6, 2026
Merged

Enable the captures PR #25 substituted with doc examples#26
leggetter merged 4 commits into
mainfrom
claude/webhook-samples-pr-review-6cqxsq

Conversation

@leggetter

Copy link
Copy Markdown
Collaborator

Follow-up work identified while reviewing #25. None of #25's own fixes are here — those are Gareth's to make on that branch.

Two of the providers #25 added doc-sourced samples for already have capture automation in this repo. This is the groundwork so the captures can actually be run, plus one publishing gap.

Scrapfly Crawler API

#25 hand-wrote 7 crawler payloads from Scrapfly's docs rather than using scripts/scrapfly/. There was a real reason: crawler deliveries set X-Scrapfly-Webhook-Resource-Type to a constant crawler and put the event name in X-Scrapfly-Crawl-Event-Name, so requestReceiver.ts collapsed every crawler event into a single crawler.json. That made them look uncapturable.

  • topic_identifier now accepts an ordered list, and requestReceiver.ts takes the first key that resolves. Crawler deliveries resolve by event name; scrape and extraction don't carry that header and fall through to resource-type exactly as before.
  • capture.ts gains a /crawl trigger (page_limit=3, max_depth=1, webhook_events unset so it subscribes to everything).
  • crawler_url_failed, crawler_url_skipped, crawler_stopped and crawler_cancelled only fire on a crawl that fails or is interrupted. They're excluded from the delete-then-wait cycle so a normal run can't destroy a sample it can't recreate — the script scrubs and reports one if it happens to land.

topic_identifier is internal to requestReceiver.tscompile.ts never publishes it — so the list form doesn't reach consumers.

Someone with the Scrapfly key needs to run yarn capture:scrapfly to land the actual files.

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 existing 2024-10 harness, which stays as the record of how those samples were captured.

  • topics.txt — 217 topics extracted from the current docs. 22 are gone since 2024-10 (purchase_orders/*, suppliers/*, translatable_content/*, checkouts/paid and others), 31 are new.
  • trigger.js — API version read from the directory name, app path and base URL from env instead of editing the file, and a fix for the off-by-one that triggered an undefined topic on the last iteration.

latest_version deliberately stays 2024-10. compile.ts publishes latest_version and the version list separately, so pointing at a version with no samples in it would break consumers. It gets bumped when the capture runs — that's step 2 in the new README.

compile.ts

doc_sourced is read from index.json but never published, so a consumer couldn't tell a doc-sourced provider from a captured one without downloading a version file. One line; it now reaches providers.json. No effect until #25 lands and there are doc-sourced providers to flag.

Testing

yarn compile passes. Receiver checked against a scratch provider tree:

Case Resolves to
crawler delivery (both headers present) crawler_started.json
scrape delivery (no crawl-event-name) scrape.json
shopify, string identifier orders.create.json
monday, body-key identifier create_pulse.json
no identifier configured untitled-<hash>.json
null body with identifier configured no longer throws

The two tsc --noEmit errors in scripts/bigcommerce and scripts/scrapfly/lib.ts are pre-existing on main and untouched here.

🤖 Generated with Claude Code

https://claude.ai/code/session_01XjdTHaFed88Xd6fBFbJTKf


Generated by Claude Code

Reviewing #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
@vercel

vercel Bot commented Aug 6, 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 6, 2026 4:01pm

Request Review

…ocked them

Running `yarn capture:scrapfly` for the first time surfaced three
problems that made the crawler captures impossible, all now fixed:

- `/crawl` takes its config as a JSON body with only `key` in the query
  string. Passing it as query params returned HTTP 400 "Invalid JSON
  payload", so the trigger never queued a job.
- Omitting `webhook_events` does not subscribe to every crawler event.
  Scrapfly defaults to the four lifecycle events, excluding the
  per-URL ones, so `crawler_url_*` never arrived. All eight are now
  named explicitly.
- `parseJobId` only read `context.job.uuid`, which is the `/scrape`
  shape. `/extraction` and `/crawl` return a flat `job_uuid`, so both
  logged "job (unknown)".

`crawler_url_skipped` moves into the clean-crawl set: a crawl bounded
by `max_depth` filters every link past the boundary, and the captured
`crawler_finished` reports 30 URLs skipped against 2 visited. Only
`crawler_url_failed`, `crawler_stopped` and `crawler_cancelled` remain
conditional.

Also: the delivery window is now overridable via
`SCRAPFLY_CAPTURE_TIMEOUT_MS` (default unchanged at 120s), since a
crawl emits `crawler_finished` only once the job completes; and expected
files are snapshotted before the run and restored if they fail to
arrive, so a failed capture can no longer destroy a good sample.

The README's claim that crawler deliveries set the resource-type header
to `crawler` is corrected — real deliveries send `crawl`.

`scrape.json` and `extraction.json` are unchanged. Scrapfly is
currently not scheduling any queued scrape or extraction job attached
to a webhook; they sit at "Pending to be scheduled" indefinitely. This
reproduces on a webhook created from scratch and has been reported to
Scrapfly. The crawler path is unaffected.

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 Author

Update: captures run, three blockers fixed

Ran yarn capture:scrapfly against a live Scrapfly key. Two claims in the description above turned out to be wrong, and a third bug surfaced. All fixed in 7a1d075.

The /crawl trigger never worked. It passed the crawl config as query params; the Crawler API takes a JSON body with only key in the query string, and returned HTTP 400 Invalid JSON payload every time. No crawl job was ever queued.

webhook_events unset does not subscribe to everything. Scrapfly defaults to crawler_started, crawler_stopped, crawler_cancelled and crawler_finished, deliberately excluding the high-frequency per-URL events. With it unset, only started and finished ever arrived. All eight are now named explicitly.

crawler_url_skipped is not a failure-only event. A crawl bounded by max_depth filters out every link past the boundary — the captured crawler_finished reports 30 URLs skipped against 2 visited. It's now in the clean-crawl set; only crawler_url_failed, crawler_stopped and crawler_cancelled remain conditional.

Five crawler samples are now committed: crawler_started, crawler_url_discovered, crawler_url_visited, crawler_url_skipped, crawler_finished.

Two smaller fixes came out of the run: parseJobId only read context.job.uuid (the /scrape shape) so /extraction and /crawl both logged job (unknown); and the delivery window is now overridable via SCRAPFLY_CAPTURE_TIMEOUT_MS, since a crawl emits crawler_finished only once the job completes.

Also corrected in providers/scrapfly/README.md: crawler deliveries send crawl in X-Scrapfly-Webhook-Resource-Type, not crawler as the Scrapfly docs state. The topic_identifier ordering is unaffected.

scrape.json and extraction.json are unchanged, deliberately

Scrapfly is currently not scheduling any queued scrape or extraction job attached to a webhook. They are accepted with a job UUID and then sit at Pending to be scheduled indefinitely — one test job stayed there 89 minutes without consuming a credit.

This is not something in this repo:

  • Send test on the webhook delivers to the same endpoint in 14ms
  • Crawler events deliver normally, seconds after the job runs
  • The identical scrape without webhook_name executes synchronously and instantly
  • A webhook created from scratch, pointing at a fresh endpoint, behaves the same

Reported to Scrapfly. The May samples remain valid, and capture.ts now restores any expected file that fails to arrive rather than leaving it deleted.

garethx added a commit to garethx/webhook-samples that referenced this pull request Aug 6, 2026
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>
All 217 topics documented at 2026-07 triggered via the Shopify CLI and
captured through Hookdeck. Nothing was skipped — every topic in
topics.txt produced a real delivery, each carrying
`x-shopify-api-version: 2026-07`.

`latest_version` moves from 2024-10 to 2026-07 now that the directory
has samples in it. 2024-10 stopped being accessible on 2025-10-16, so
the previous value pointed at a version Shopify no longer serves.

Eleven samples have a `{}` body: Shopify's trigger sends no payload for
those topics. That's the same behaviour as 2024-10, which has seven,
and `shipping_addresses/*` are empty in both. The headers are real
either way.

The 2026-07 README gains the two things that cost time in this run:
the CLI destination path must be `/` because trigger.js appends
`/shopify/<version>` to the address itself — passing `--path` the way
scripts/scrapfly does doubles it and every delivery 404s, and the value
is stored server-side so restarting `hookdeck listen` won't clear it —
and `shopify app webhook trigger` starts a device-code login when the
CLI isn't authenticated, which would stall an unattended run.

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 Author

Update: Shopify 2026-07 captured, latest_version bumped

All 217 topics triggered via the Shopify CLI and captured through Hookdeck — 0 skipped. Every sample carries x-shopify-api-version: 2026-07. latest_version now moves to 2026-07 as step 2 of the README describes, and yarn compile publishes versions: ["2023-01", "2024-10", "2026-07"] with doc_sourced: false.

Worth knowing for anyone re-running it — neither is obvious and both cost time:

The CLI destination path must be /. trigger.js appends /shopify/<version> to WEBHOOK_BASE_URL itself, so passing --path /shopify/2026-07 to hookdeck listen (as scripts/scrapfly/ does, because Scrapfly posts to the bare source URL) forwards to /shopify/2026-07/shopify/2026-07 and every delivery 404s. The path is stored on the auto-created CLI destination, so restarting listen without the flag does not clear it — it has to be PUT back to / via the API.

Auth is interactive. shopify app webhook trigger starts a device-code login if the CLI isn't authenticated, which would stall an unattended 217-topic run. Trigger one topic by hand first.

Both are now documented in scripts/shopify/2026-07/README.md.

Eleven samples have a {} body — Shopify sends no payload for those topics. Not a capture fault: 2024-10 has the same thing for seven topics, and shipping_addresses/* are empty in both versions. Headers are real regardless.

`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
leggetter merged commit 0cf622e into main Aug 6, 2026
3 checks passed
@leggetter
leggetter deleted the claude/webhook-samples-pr-review-6cqxsq branch August 6, 2026 16:46
garethx added a commit to garethx/webhook-samples that referenced this pull request Aug 6, 2026
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>
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