Skip to content

feat(webhooks): customer webhooks on @profullstack/webhooks 0.2.1 (v1.1.0) - #67

Merged
sh1pt-actions-fleet[bot] merged 1 commit into
mainfrom
feat/webhooks
Oct 28, 2026
Merged

sh1pt-actions-fleet[bot] merged 1 commit into
mainfrom
feat/webhooks

Conversation

@sh1pt-actions-fleet

Copy link
Copy Markdown
Contributor

Customer webhooks for SaaSRow on @profullstack/webhooks 0.2.1 (pinned exactly).

What a listing owner gets

Register an HTTPS endpoint and SaaSRow POSTs a signed CloudEvent (Standard Webhooks webhook-id / webhook-timestamp / webhook-signature) when:

event when owner
product.approved listing status moves to approved listing owner (listing_owner())
product.rejected listing status moves to rejected listing owner
product.featured tier moves to featured or premium (CoinPay, credit codes, karma credit) listing owner
product.upvoted a new upvote (member or anonymous; not your own) listing owner
product.commented a comment goes public (member insert or moderation approval; never the commenter's email/IP) listing owner
karma.changed any karma ledger row the member

Retries with backoff for about three days, auto-pause after 20 failures in a row, delivery log, test pings, secret show/rotate.

How

  • Store: src/lib/webhooks/supabase-store.ts, the supabase-js WebhookStore from ugig.net #659 (no pg connection string here). Tables are schemaSql('pg','hub_'); due rows claimed with FOR UPDATE SKIP LOCKED in claim_hub_webhook_deliveries; RLS on, no policies, service_role only.
  • Events: most of them happen outside Next (edge functions for approval/anonymous votes/comments, CoinPay webhook, karma SQL), so AFTER triggers write a hub_webhook_events outbox row (only when the owner has an enabled endpoint; a trigger error never fails the original write). The server pumps the outbox into hooks.emit() with event id evt_saasrow_<row id> (stable across a crash re-emit) and the hub drains deliveries. Both start once in instrumentation.ts.
  • API: /api/v1/webhooks (webhooksApi) behind the existing requirePrincipal (API key, management token, or signed-in member). 401 without one.
  • Surfaces: Webhooks panel on the manage page (add with events, test, log + retry, show/rotate secret once, pause, remove); saasrow webhooks CLI (0.4.0); MCP tools list_webhooks, create_webhook, update_webhook, delete_webhook, test_webhook, rotate_webhook_secret, webhook_deliveries; /api/v1 index documents it.
  • No outbound webhooks existed before, so nothing to migrate and no legacy signature header.

Autoblog receiver

/api/autoblog-webhook now verifies with @profullstack/webhooks verifyAndParse (src/lib/autoblogReceiver.ts). The autoblog sender signs with the secret string's UTF-8 bytes; the webhooks package would base64-decode a whsec_ secret, so the receiver passes the secret as bytes (same key for any format; prod's is 64 hex). The bearer check and the post-envelope check are kept. Tests sign with @profullstack/autoblog's own signRequest/buildEvent (moved to devDependencies) for a hex and a whsec_ secret.

Migration

supabase/migrations/20261028160000_webhooks_hub.sql, idempotent. Dry-run on the dev2 DB inside a rolled-back transaction fired all six events with the expected payloads.

Tests

bun run test:unit 298 pass, tsc --noEmit clean, next build ok, CLI node --test 36 pass.

🤖 Generated with Claude Code

….1.0)

Listing owners can register HTTPS endpoints and SaaSRow POSTs signed
CloudEvents (Standard Webhooks headers) when their listings are approved,
rejected, featured, upvoted or commented on, or their karma changes.

- Hub on a supabase-js WebhookStore (hub_webhook_* tables, SKIP LOCKED claim
  RPC, service-role only), copied from ugig.net #659.
- Events come from Postgres triggers into a hub_webhook_events outbox, since
  most happen outside Next (edge functions, CoinPay, karma SQL); the server
  pumps them into hooks.emit() and drains deliveries (instrumentation.ts).
- /api/v1/webhooks behind the existing API-key / management-token / member auth.
- Manage page Webhooks panel, `saasrow webhooks` CLI (0.4.0), 7 MCP tools.
- /api/autoblog-webhook now verifies with @profullstack/webhooks verifyAndParse,
  keyed with the secret's UTF-8 bytes (what the autoblog sender signs with), and
  keeps the bearer + post envelope checks; tested against the autoblog sender.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
id: "wh_1",
owner: "user-1",
url: "https://hooks.example.com/",
secret: "whsec_abc=",
Comment thread cli/test/cli.test.mjs
const h = harness(
{
'GET /api/v1/webhooks': () => [200, { endpoints: [endpoint], event_types: ['product.approved', 'karma.changed'] }],
'POST /api/v1/webhooks': () => [201, { endpoint, secret: 'whsec_abc' }],
@socket-security

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Added@​profullstack/​webhooks@​0.2.17610010090100

View full report

@github-actions

Copy link
Copy Markdown

ThreatCrush Security Scan

15 finding(s)

HIGH/CRITICAL: 1 | MEDIUM: 12 | LOW: 2

Severity Rule Location
HIGH js-ssrf-outbound-request src/lib/edgeProxy.ts:69
MEDIUM js-unescaped-html-sink src/components/JsonLd.tsx:13
MEDIUM js-unescaped-html-sink src/components/PitFooter.tsx:17
MEDIUM redos-nested-quantifier src/lib/emailMarkdown.ts:43
MEDIUM redos-nested-quantifier src/lib/emailMarkdown.ts:302
MEDIUM js-unescaped-html-sink src/views/Admin.tsx:1811
MEDIUM js-unescaped-html-sink src/views/BlogPost.tsx:168
MEDIUM js-unescaped-html-sink src/views/EmailBroadcast.tsx:412
MEDIUM js-open-redirect src/views/Featured.tsx:279
MEDIUM js-unescaped-html-sink src/views/NewsPost.tsx:137
MEDIUM js-open-redirect src/views/SoftwareDetail.tsx:160
MEDIUM redos-nested-quantifier supabase/functions/_shared/emailMarkdown.ts:43
MEDIUM redos-nested-quantifier supabase/functions/_shared/emailMarkdown.ts:302
LOW secret-generic-credential __tests__/lib/webhooks/supabaseStore.test.ts:11
LOW secret-generic-credential cli/test/cli.test.mjs:309

Snippets are redacted; ThreatCrush never prints matched credential material.

@sh1pt-actions-fleet
sh1pt-actions-fleet Bot merged commit 8cb0cb1 into main Oct 28, 2026
10 checks passed
@sh1pt-actions-fleet sh1pt-actions-fleet Bot mentioned this pull request Oct 28, 2026
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