CLI

Send & Verify Events

Send correctly signed synthetic Stripe, GitHub, Shopify, and Slack events, and verify captured signatures.

Docs for LLMs

Build a webhook handler with no provider account, dashboard, or real event. otterkit send fires a correctly signed synthetic event straight at your local handler, and otterkit verify tells you whether captured requests carry a valid signature. Both run entirely on your machine, and the signing secret never leaves it.

Send a signed event#

Pass the provider's signing secret and the event is signed exactly the way the provider signs it, so your handler's verification passes:

Terminal
# Stripe: send a signed payment_intent.succeeded to your handler
npx otterkit send stripe:payment_intent.succeeded 127.0.0.1:3000/webhooks/stripe \
  --secret whsec_your_signing_secret

# GitHub, Shopify, and Slack work the same way
npx otterkit send github:push 127.0.0.1:3000/hooks/github --secret my-webhook-secret
npx otterkit send shopify:orders/create 127.0.0.1:3000/hooks --secret shpss_...

# Customize the payload
npx otterkit send stripe:checkout.session.completed 127.0.0.1:3000/hooks \
  --secret whsec_... --body '{"data":{"object":{"amount_total":19900}}}'

These events are built in:

ProviderEvents
stripepayment_intent.succeeded, checkout.session.completed, invoice.paid, customer.subscription.deleted
githubpush, pull_request.opened, issues.opened
shopifyorders/create, orders/paid
slackevent_callback.message, url_verification

Without --secret the event is sent unsigned, which is useful for checking that your handler rejects unsigned requests. Add --json for a machine-readable result.

Verify captured signatures#

The most common webhook question is “is my signature check wrong, or is the payload wrong?” Point verify at a capture log with the secret and it gives a clear answer:

Terminal
npx otterkit verify myhooks stripe --secret whsec_...
# ✓ 08:14:02.101  POST /webhooks/stripe
# ✗ 08:15:11.882  POST /webhooks/stripe
#     Stripe-Signature: signature_mismatch

If a request fails with the correct secret, the payload was altered in transit or your code isn't verifying the raw bytes. If every request passes, the bug is in your handler. verify checks the last 20 requests by default; change that with --last <n>. It supports Stripe, GitHub, Shopify, and Slack. For the other providers, use server-side --verify on the webhook endpoint.

Replay with re-signing#

Editing the body of a captured signed request breaks its signature, so your handler rejects the replay. --resign signs the edited body again so it still passes:

Terminal
npx otterkit replay myhooks --set data.object.amount=999 \
  --resign stripe --secret whsec_...