Skip to content

feat: add webhook subscription system#263

Open
GreatShinro wants to merge 4 commits into
Neurowealth:mainfrom
GreatShinro:feat/webhook-system
Open

feat: add webhook subscription system#263
GreatShinro wants to merge 4 commits into
Neurowealth:mainfrom
GreatShinro:feat/webhook-system

Conversation

@GreatShinro

Copy link
Copy Markdown

feat: Webhook Subscription System

Summary

Adds a full server-push webhook system so integrators (mobile apps, dashboards) can receive real-time event notifications without polling.


What Changed

Database

Model Purpose
WebhookSubscription Stores subscriber URL, event filters, HMAC secret, and active state per user
WebhookDelivery Immutable delivery log — tracks attempts, HTTP status, and error per dispatch

Migration: prisma/migrations/20260627000000_add_webhook_tables/


API — POST /api/webhooks (new endpoint group)

All routes require Authorization: Bearer <JWT>.

Method Path Description
POST /api/webhooks Create subscription — returns secret once
GET /api/webhooks List subscriptions (no secrets)
GET /api/webhooks/:id Get single subscription
PATCH /api/webhooks/:id Update URL / events / active state
DELETE /api/webhooks/:id Delete subscription + delivery history

Payload Signing

Every outbound webhook POST carries:

X-Neurowealth-Signature: sha256=<hmac-hex>

Computed as HMAC-SHA256(secret, raw_body). Recipients verify by recomputing with their stored secret.


Events Dispatched

Event Fired from
transaction.confirmed transaction-controller.ts — on-chain tx confirmed
deposit.received stellar/events.ts — deposit event processed
withdraw.completed stellar/events.ts — withdraw event processed
agent.rebalanced stellar/events.ts + agent/loop.ts — rebalance executed

Retry Logic

Failed deliveries are retried up to 3 times with exponential back-off:

attempt 1 → immediate
attempt 2 → wait 1 s
attempt 3 → wait 2 s

Each attempt is logged in WebhookDelivery. After all attempts fail the record is marked FAILED and logged as an error. Dispatch is always fire-and-forget — failures never block the request path.


Files Changed

prisma/schema.prisma                              ← new models + relation
prisma/migrations/20260627000000_add_webhook_tables/migration.sql

src/utils/webhookSignature.ts                     ← generateSecret + signPayload
src/services/webhookDispatcher.ts                 ← dispatch + retry + delivery log
src/routes/webhooks.ts                            ← CRUD router
src/validators/webhook-validators.ts              ← Zod schemas

src/index.ts                                      ← mount /api/webhooks
src/stellar/events.ts                             ← fire deposit/withdraw/rebalance events
src/agent/loop.ts                                 ← fire agent.rebalanced on rebalance
src/controllers/transaction-controller.ts         ← fire transaction.confirmed

tests/unit/utils/webhookSignature.test.ts         ← 5 tests
tests/unit/services/webhookDispatcher.test.ts     ← 8 tests

docs/openapi.yaml                                 ← webhooks tag + schemas + paths
README.md                                         ← API table updated

Tests

PASS  tests/unit/utils/webhookSignature.test.ts    (5 tests)
PASS  tests/unit/services/webhookDispatcher.test.ts (8 tests)

Covers: secret uniqueness, HMAC correctness, success on first attempt, exhausted retry → FAILED, partial retry → SUCCESS, signature header format, subscription event filtering.


Acceptance Criteria

  • POST /api/webhooks creates subscription, returns signing secret once
  • Payload signed with HMAC-SHA256, verifiable by recipient via X-Neurowealth-Signature
  • Failed deliveries retried (≤3) and logged in WebhookDelivery table
  • Unit tests for signature generation and retry logic
  • OpenAPI spec updated

Testing Locally

# 1. Apply migration
npx prisma migrate dev

# 2. Create a subscription
curl -X POST http://localhost:3000/api/webhooks \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://webhook.site/your-id","events":["deposit.received","transaction.confirmed"]}'
# → response includes `secret` — save it

# 3. Verify a delivery signature on receipt
echo -n '<raw_body>' | openssl dgst -sha256 -hmac '<secret>'
# should match X-Neurowealth-Signature header (minus "sha256=" prefix)

closes #226

- Add WebhookSubscription and WebhookDelivery Prisma models
- CRUD endpoints under /api/webhooks (Bearer JWT auth)
- HMAC-SHA256 payload signing via X-Neurowealth-Signature header
- Dispatch with 3-attempt exponential back-off, persisted delivery log
- Wire dispatch into stellar events, agent loop, and tx controller
- Unit tests for signature generation and retry logic (13 tests)
- OpenAPI spec updated with webhooks tag and schemas
@drips-wave

drips-wave Bot commented Jun 27, 2026

Copy link
Copy Markdown

@GreatShinro Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

- lint-staged runs eslint --fix + prettier --write on staged src/tests *.ts
- pre-push runs full test suite with --passWithNoTests
- prepare script wires husky on npm install
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.

Add webhook push notifications for transaction and agent events

1 participant