@spoke-labs/hooks
v0.0.3
Published
**Your production webhooks are your best test suite.**
Readme
Spoke Hooks
Your production webhooks are your best test suite.
Spoke Hooks replays real webhook events against your application and catches behavioral regressions before they reach production.
It is built for developers who want webhook regressions to fail in CI, not in production.
Install
Install Spoke Hooks as a development dependency:
npm install -D @spoke-labs/hooksInitialize it in your project:
npx spoke-hooks initThis creates:
.spoke/
├── config.json
└── events/Add a Stripe event:
npx spoke-hooks add stripe-event.jsonRun your webhook application in a known-good state and record its behavior:
npx spoke-hooks baselineAfter changing your application code, replay the same events:
npx spoke-hooks testIf the behavior changes, Spoke Hooks exits with status code 1, making the same command usable directly in CI.
Why
Webhook integrations are easy to break.
A handler can still compile, unit tests can still pass, and synthetic test payloads can still look correct while a real webhook event behaves differently.
Real webhook traffic contains edge cases that synthetic fixtures often miss:
- unexpected optional fields
- real provider payload shapes
- historical event variations
- duplicate delivery behavior
- delayed events
- ordering differences
Spoke Hooks turns real webhook events into regression tests.
Core idea
The workflow is intentionally simple:
real webhook event
↓
store as fixture
↓
record known-good behavior
↓
change application code
↓
replay same event
↓
compare behavior
↓
PASS / FAILThe goal is to make webhook behavior testable in the same way developers already test code changes in CI.
Quick start
1. Initialize Spoke Hooks
From the root of your application:
npx spoke-hooks initDefault configuration:
{
"endpoint": "http://localhost:3000/webhook",
"eventsDir": ".spoke/events",
"timeoutMs": 5000
}Edit .spoke/config.json if your webhook endpoint or timeout is different.
2. Add a Stripe event
Given a Stripe event such as:
{
"id": "evt_invoice_paid_001",
"type": "invoice.paid",
"data": {
"object": {
"id": "in_001",
"status": "paid"
}
}
}Import it:
npx spoke-hooks add stripe-event.jsonSpoke Hooks stores the event as a fixture under:
.spoke/events/The original Stripe payload is preserved inside the fixture.
At this point no expected behavior has been recorded yet.
Running:
npx spoke-hooks testwill fail with:
Event: invoice.paid
FAIL: No baseline recorded. Run `spoke-hooks baseline` first.and exit with status code:
13. Record a baseline
Start your application in a known-good state.
Then run:
npx spoke-hooks baselineSpoke Hooks sends the stored webhook payload to the configured endpoint and records the response.
Example:
Event: invoice.paid
Baseline updated:
{ status: 200, body: { received: true } }The fixture now contains the known-good behavior:
{
"event": {
"id": "evt_invoice_paid_001",
"provider": "stripe",
"type": "invoice.paid",
"payload": {
"id": "evt_invoice_paid_001",
"type": "invoice.paid",
"data": {
"object": {
"id": "in_001",
"status": "paid"
}
}
}
},
"baseline": {
"status": 200,
"body": {
"received": true
}
}
}4. Change your application
Make changes to your webhook handler as usual.
Your stored event and its known-good baseline stay unchanged.
5. Test for regressions
Run:
npx spoke-hooks testIf the current application behaves like the recorded baseline:
Event: invoice.paid
Expected: { status: 200, body: { received: true } }
Actual: { status: 200, body: { received: true } }
PASSExit code:
0If a code change introduces a regression:
Event: invoice.paid
Expected: { status: 200, body: { received: true } }
Actual: { status: 500, body: { received: true } }
FAILExit code:
1That non-zero exit code causes CI jobs to fail.
CLI
Current V0 commands:
npx spoke-hooks init
npx spoke-hooks add <event.json>
npx spoke-hooks baseline
npx spoke-hooks test
npx spoke-hooks --versioninit
Creates the Spoke Hooks project structure:
npx spoke-hooks initCreates:
.spoke/config.json
.spoke/events/add
Imports a Stripe event into the fixture corpus:
npx spoke-hooks add stripe-event.jsonExample output:
Added Stripe event: invoice.payment_failed
Fixture: /path/to/project/.spoke/events/invoice.payment_failed-evt_123.json
Run `spoke-hooks baseline` to record expected behavior.baseline
Replays every stored fixture against the configured webhook endpoint and records the current response as expected behavior:
npx spoke-hooks baselineUse this while your application is in a known-good state.
test
Replays the stored fixtures without changing their baselines:
npx spoke-hooks testThe current response is compared against the recorded response.
A difference produces:
FAILand exit code 1.
--version
Prints the installed CLI version:
npx spoke-hooks --versionWhat is compared
Current V0 regression checks compare:
- HTTP response status
- response body
Spoke Hooks also treats runtime replay failures as test failures, including:
- endpoint connection failures
- request timeouts
Error handling
Endpoint unavailable
If the webhook server is not running:
ERROR: Could not connect to webhook endpoint: http://localhost:3000/webhookExit code:
1Request timeout
If the webhook handler does not respond before the configured timeout:
ERROR: Webhook request timed out after 5000 ms: http://localhost:3000/webhookExit code:
1Configuration
Spoke Hooks reads configuration from:
.spoke/config.jsonDefault configuration:
{
"endpoint": "http://localhost:3000/webhook",
"eventsDir": ".spoke/events",
"timeoutMs": 5000
}endpoint
The local webhook endpoint Spoke Hooks should replay events against.
Example:
{
"endpoint": "http://localhost:3000/webhook"
}eventsDir
Directory containing webhook fixtures.
Default:
.spoke/eventstimeoutMs
Maximum time Spoke Hooks waits for the webhook endpoint to respond.
Default:
5000GitHub Actions
Spoke Hooks is designed to work as a normal CI command.
Once your application is running inside the CI job, run:
- name: Run webhook regression tests
run: npx spoke-hooks testA webhook regression causes the command to exit with status code 1, which fails the GitHub Actions step.
Your .spoke/events fixtures and recorded baselines can live in the repository alongside the application code.
Current V0 scope
The current version intentionally focuses on a narrow workflow:
- Stripe
- Node.js applications
- Express-compatible HTTP endpoints
- JSON event fixtures
- local HTTP replay
- baseline recording
- HTTP status comparison
- response body comparison
- timeout handling
- connection error handling
- CI-friendly exit codes
- GitHub Actions usage
The narrow scope is intentional.
Spoke Hooks is currently not:
- a webhook delivery platform
- a webhook forwarding proxy
- a hosted dashboard
- a production webhook gateway
- a replacement for Stripe's developer tooling
- a multi-provider webhook platform
The current goal is one thing:
turn webhook events that already happened into regression tests for the code you are changing today.
Repository structure
spoke-hooks/
├── .github/
│ └── workflows/
│ └── m0.yml
├── examples/
│ └── express-stripe/
├── packages/
│ ├── cli/
│ ├── core/
│ └── stripe/
├── package.json
├── tsconfig.json
└── README.md@spoke-labs/hooks
User-facing CLI package.
Provides:
spoke-hooks@spoke-labs/core
Core fixture, replay, configuration, discovery, comparison, and shared type functionality.
@spoke-labs/stripe
Stripe-specific event import and normalization functionality.
Users normally only need to install:
npm install -D @spoke-labs/hooksThe internal packages are installed automatically as dependencies.
Development
Clone the repository and install workspace dependencies:
npm installBuild all packages:
npm run buildRun TypeScript checks:
npm run typecheckStatus
Spoke Hooks is currently an early V0 focused on validating the webhook regression testing workflow.
The product surface is intentionally small while the core behavior is being validated with real developer usage.
License
License information will be added before a stable release.
