@opencard-dev/webhook-server 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,46 @@
1
+ /**
2
+ * OpenCard Webhook Server
3
+ * =======================
4
+ * A lightweight HTTP server built with Hono that receives real-time
5
+ * authorization requests from Stripe and responds with approve/decline
6
+ * decisions based on the card's spend rules.
7
+ *
8
+ * ─── Why Hono? ──────────────────────────────────────────────────────────────
9
+ * Hono is a tiny, fast web framework — roughly 14KB. We chose it over Express
10
+ * because it's TypeScript-native, has cleaner middleware, and is significantly
11
+ * faster. Speed matters here: Stripe gives us ~2 seconds to respond to
12
+ * authorization requests before automatically declining.
13
+ *
14
+ * ─── The one endpoint that matters ─────────────────────────────────────────
15
+ * POST /webhooks/stripe
16
+ * ↑ Stripe calls this in real time for every card event.
17
+ * We verify the signature, parse the event, and route it to a handler.
18
+ * For authorization requests, we return { approved: true/false }.
19
+ *
20
+ * ─── Security ───────────────────────────────────────────────────────────────
21
+ * Stripe signs every webhook with HMAC-SHA256 using a shared secret.
22
+ * We verify this signature before processing ANYTHING. An invalid signature
23
+ * means the request didn't come from Stripe — we reject it with a 400.
24
+ *
25
+ * ─── Running the server ─────────────────────────────────────────────────────
26
+ * Set env vars and start:
27
+ * STRIPE_SECRET_KEY=sk_test_... \
28
+ * STRIPE_WEBHOOK_SECRET=whsec_... \
29
+ * PORT=3000 \
30
+ * node dist/index.js
31
+ *
32
+ * Get your webhook secret from the Stripe CLI:
33
+ * stripe listen --forward-to localhost:3000/webhooks/stripe
34
+ */
35
+ import { Hono } from 'hono';
36
+ /**
37
+ * Create and return the Hono webhook application.
38
+ * This is exported so the serve command can reuse the same app.
39
+ */
40
+ export declare function createWebhookApp(): Hono<import("hono/types").BlankEnv, import("hono/types").BlankSchema, "/">;
41
+ /**
42
+ * Start the webhook server on the specified port.
43
+ * Returns a promise that resolves when the server is ready.
44
+ */
45
+ export declare function startWebhookServer(port: number): Promise<void>;
46
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAGH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAyU5B;;;GAGG;AACH,wBAAgB,gBAAgB,+EAE/B;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA4C9D"}
package/dist/index.js ADDED
@@ -0,0 +1,389 @@
1
+ "use strict";
2
+ /**
3
+ * OpenCard Webhook Server
4
+ * =======================
5
+ * A lightweight HTTP server built with Hono that receives real-time
6
+ * authorization requests from Stripe and responds with approve/decline
7
+ * decisions based on the card's spend rules.
8
+ *
9
+ * ─── Why Hono? ──────────────────────────────────────────────────────────────
10
+ * Hono is a tiny, fast web framework — roughly 14KB. We chose it over Express
11
+ * because it's TypeScript-native, has cleaner middleware, and is significantly
12
+ * faster. Speed matters here: Stripe gives us ~2 seconds to respond to
13
+ * authorization requests before automatically declining.
14
+ *
15
+ * ─── The one endpoint that matters ─────────────────────────────────────────
16
+ * POST /webhooks/stripe
17
+ * ↑ Stripe calls this in real time for every card event.
18
+ * We verify the signature, parse the event, and route it to a handler.
19
+ * For authorization requests, we return { approved: true/false }.
20
+ *
21
+ * ─── Security ───────────────────────────────────────────────────────────────
22
+ * Stripe signs every webhook with HMAC-SHA256 using a shared secret.
23
+ * We verify this signature before processing ANYTHING. An invalid signature
24
+ * means the request didn't come from Stripe — we reject it with a 400.
25
+ *
26
+ * ─── Running the server ─────────────────────────────────────────────────────
27
+ * Set env vars and start:
28
+ * STRIPE_SECRET_KEY=sk_test_... \
29
+ * STRIPE_WEBHOOK_SECRET=whsec_... \
30
+ * PORT=3000 \
31
+ * node dist/index.js
32
+ *
33
+ * Get your webhook secret from the Stripe CLI:
34
+ * stripe listen --forward-to localhost:3000/webhooks/stripe
35
+ */
36
+ var __importDefault = (this && this.__importDefault) || function (mod) {
37
+ return (mod && mod.__esModule) ? mod : { "default": mod };
38
+ };
39
+ Object.defineProperty(exports, "__esModule", { value: true });
40
+ exports.createWebhookApp = createWebhookApp;
41
+ exports.startWebhookServer = startWebhookServer;
42
+ const node_server_1 = require("@hono/node-server");
43
+ const hono_1 = require("hono");
44
+ const stripe_1 = __importDefault(require("stripe"));
45
+ const core_1 = require("@opencard-dev/core");
46
+ // ─── Configuration ────────────────────────────────────────────────────────────
47
+ /**
48
+ * The port this server listens on. Default 3000.
49
+ * Override with the PORT environment variable.
50
+ */
51
+ const PORT = parseInt(process.env.PORT || '3000', 10);
52
+ /**
53
+ * Your Stripe secret key. Used to initialize the Stripe SDK so we can
54
+ * call Stripe's API (e.g., to update card status after a decision).
55
+ * Must start with sk_test_... for test mode, sk_live_... for production.
56
+ */
57
+ const STRIPE_SECRET_KEY = process.env.STRIPE_SECRET_KEY;
58
+ /**
59
+ * The webhook signing secret Stripe uses to sign webhook payloads.
60
+ * Get this from:
61
+ * - Stripe CLI: `stripe listen` prints it on first run
62
+ * - Stripe Dashboard: Developers → Webhooks → your endpoint → Signing secret
63
+ *
64
+ * Without this, we can't verify that webhook requests actually came from Stripe.
65
+ * The server will still start without it (for ease of local dev), but will
66
+ * print a clear warning and skip verification.
67
+ */
68
+ const STRIPE_WEBHOOK_SECRET = process.env.STRIPE_WEBHOOK_SECRET;
69
+ // ─── Initialize Stripe (deferred) ──────────────────────────────────────────────
70
+ /**
71
+ * The Stripe SDK instance. Created lazily when the server starts.
72
+ * This allows the module to be imported without requiring STRIPE_SECRET_KEY.
73
+ */
74
+ let stripe = null;
75
+ // ─── Server State ───────────────────────────────────────────────────────────
76
+ /**
77
+ * Track when the server started so we can report uptime in health checks.
78
+ */
79
+ const startTime = Date.now();
80
+ // ─── Hono App ─────────────────────────────────────────────────────────────────
81
+ /**
82
+ * Create the Hono application.
83
+ * Hono is structured as an app with routes — similar to Express but more
84
+ * TypeScript-friendly. Each route is defined with app.get/post/etc.
85
+ */
86
+ const app = new hono_1.Hono();
87
+ // ─── Health check ─────────────────────────────────────────────────────────────
88
+ /**
89
+ * GET /health
90
+ * A simple endpoint that returns 200 OK.
91
+ * Used by monitoring systems, load balancers, and developers to confirm
92
+ * the server is up and responding. The webhook doesn't do any Stripe logic here.
93
+ */
94
+ app.get('/health', (c) => {
95
+ const uptime = Math.floor((Date.now() - startTime) / 1000); // seconds
96
+ return c.json({
97
+ status: 'ok',
98
+ service: 'opencard-webhook-server',
99
+ version: '0.1.0',
100
+ uptime, // seconds
101
+ timestamp: new Date().toISOString(),
102
+ // Tell the caller whether signature verification is active.
103
+ // If this shows false in production, something is wrong.
104
+ signatureVerification: !!STRIPE_WEBHOOK_SECRET,
105
+ });
106
+ });
107
+ // ─── Webhook handler ──────────────────────────────────────────────────────────
108
+ /**
109
+ * POST /webhooks/stripe
110
+ * ──────────────────────
111
+ * The main endpoint. Stripe calls this for every Issuing event.
112
+ *
113
+ * IMPORTANT: Stripe signature verification requires the raw request body
114
+ * (the exact bytes Stripe sent), not a parsed JSON object. If we let Hono
115
+ * parse the body first, the byte-for-byte signature check will fail.
116
+ * That's why we read `req.arrayBuffer()` and convert to text manually.
117
+ */
118
+ app.post('/webhooks/stripe', async (c) => {
119
+ // ── Step 1: Read the raw request body ────────────────────────────────────
120
+ //
121
+ // We need the raw bytes for signature verification. Hono doesn't parse
122
+ // the body automatically, so we read it ourselves.
123
+ const rawBody = await c.req.text();
124
+ // ── Step 2: Get the Stripe signature header ───────────────────────────────
125
+ //
126
+ // Stripe attaches a `Stripe-Signature` header to every webhook request.
127
+ // It contains a timestamp and an HMAC signature computed from the body.
128
+ // We need this to verify the request is authentic.
129
+ const signature = c.req.header('stripe-signature');
130
+ if (!signature) {
131
+ // No signature header at all — definitely not a legitimate Stripe request.
132
+ console.warn('[Webhook] Request missing Stripe-Signature header — rejecting');
133
+ return c.json({ error: 'Missing Stripe-Signature header' }, 400);
134
+ }
135
+ // ── Step 3: Verify the signature (if we have a secret) ───────────────────
136
+ //
137
+ // stripe.webhooks.constructEvent() does three things:
138
+ // 1. Parses the raw body as JSON to get the event object
139
+ // 2. Verifies the HMAC signature using our webhook secret
140
+ // 3. Checks that the timestamp isn't too old (prevents replay attacks)
141
+ //
142
+ // If any of those checks fail, it throws an error.
143
+ let event;
144
+ const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET;
145
+ if (!stripe) {
146
+ console.error('[Webhook] Stripe not initialized — server not started with startWebhookServer()');
147
+ return c.json({ error: 'Server not initialized' }, 500);
148
+ }
149
+ if (webhookSecret) {
150
+ try {
151
+ event = stripe.webhooks.constructEvent(rawBody, signature, webhookSecret);
152
+ }
153
+ catch (err) {
154
+ // Signature invalid or timestamp too old. This could mean:
155
+ // - Someone is trying to send fake events (attack)
156
+ // - The webhook secret is misconfigured
157
+ // - The request was somehow corrupted in transit
158
+ console.error('[Webhook] Signature verification failed:', err instanceof Error ? err.message : err);
159
+ return c.json({ error: 'Webhook signature verification failed' }, 400);
160
+ }
161
+ }
162
+ else {
163
+ // No webhook secret configured — skip verification.
164
+ // Parse the body directly. Only acceptable in local development.
165
+ try {
166
+ event = JSON.parse(rawBody);
167
+ }
168
+ catch {
169
+ return c.json({ error: 'Invalid JSON body' }, 400);
170
+ }
171
+ }
172
+ // ── Step 4: Route the event to the right handler ─────────────────────────
173
+ //
174
+ // Stripe sends different event types for different things. We pattern-match
175
+ // on `event.type` to decide what to do.
176
+ //
177
+ // Reference: https://stripe.com/docs/api/events/types
178
+ console.log(`[Webhook] Received event: ${event.type} (id: ${event.id})`);
179
+ switch (event.type) {
180
+ // ── issuing_authorization.request ──────────────────────────────────────
181
+ //
182
+ // THE critical real-time event. Stripe sends this synchronously when a
183
+ // card is presented for payment. We MUST respond within ~2 seconds with
184
+ // our approve/decline decision.
185
+ //
186
+ // The response body is what Stripe actually uses to decide:
187
+ // { approved: true } → Stripe approves the charge
188
+ // { approved: false } → Stripe declines the charge
189
+ //
190
+ // Note: For this event type, Stripe expects a specific response format.
191
+ // Any non-200 response or a response missing `approved` will cause
192
+ // Stripe to decline by default.
193
+ case 'issuing_authorization.request': {
194
+ const authorization = event.data.object;
195
+ // handleAuthorizationRequest is now async (it looks up rules from the store)
196
+ const result = await (0, core_1.handleAuthorizationRequest)(authorization);
197
+ // Return the decision to Stripe. This is the actual approve/decline.
198
+ return c.json({ approved: result.approved });
199
+ }
200
+ // ── issuing_authorization.created ──────────────────────────────────────
201
+ //
202
+ // Fired AFTER an authorization is definitively approved or declined.
203
+ // This is NOT the real-time request — it's a notification after the fact.
204
+ // We log it but don't need to return any special response.
205
+ case 'issuing_authorization.created': {
206
+ const authorization = event.data.object;
207
+ (0, core_1.handleAuthorizationCreated)(authorization);
208
+ return c.json({ received: true });
209
+ }
210
+ // ── issuing_transaction.created ────────────────────────────────────────
211
+ //
212
+ // Fired when a transaction is fully captured (money actually moved).
213
+ // This can happen hours or days after the authorization, and the final
214
+ // amount may differ from the authorization amount (e.g. tips at restaurants).
215
+ case 'issuing_transaction.created': {
216
+ const transaction = event.data.object;
217
+ (0, core_1.handleTransactionCreated)(transaction);
218
+ return c.json({ received: true });
219
+ }
220
+ // ── issuing_card.updated ───────────────────────────────────────────────
221
+ //
222
+ // Fired when a card's status, spending limits, or metadata changes.
223
+ // Useful for tracking when agents' cards are paused or resumed.
224
+ case 'issuing_card.updated': {
225
+ const card = event.data.object;
226
+ (0, core_1.handleCardUpdated)(card);
227
+ return c.json({ received: true });
228
+ }
229
+ // ── Unhandled event types ──────────────────────────────────────────────
230
+ //
231
+ // Stripe may send many other event types. We acknowledge receipt (200 OK)
232
+ // without doing anything — this prevents Stripe from retrying the event.
233
+ // Silently ignoring is intentional; we only care about Issuing events.
234
+ default:
235
+ console.log(`[Webhook] Unhandled event type: ${event.type} — acknowledged and ignored`);
236
+ return c.json({ received: true });
237
+ }
238
+ });
239
+ // ─── Approval API endpoints ───────────────────────────────────────────────────
240
+ /**
241
+ * GET /approvals
242
+ * List pending approval requests. Useful for building custom approval UIs,
243
+ * Slack bots, or mobile apps that show pending requests.
244
+ */
245
+ app.get('/approvals', (c) => {
246
+ try {
247
+ const db = new core_1.OpenCardDatabase();
248
+ const pending = db.listPendingApprovalRequests();
249
+ db.close();
250
+ return c.json({ pending });
251
+ }
252
+ catch (err) {
253
+ console.error('[Approvals] Error listing requests:', err);
254
+ return c.json({ error: 'Failed to list approval requests' }, 500);
255
+ }
256
+ });
257
+ /**
258
+ * POST /approvals/:id/approve
259
+ * Approve a pending request. Body: { decided_by: string, note?: string }
260
+ */
261
+ app.post('/approvals/:id/approve', async (c) => {
262
+ const id = c.req.param('id');
263
+ try {
264
+ const body = await c.req.json();
265
+ const decidedBy = body.decided_by || 'api';
266
+ const db = new core_1.OpenCardDatabase();
267
+ const request = db.approveRequest(id, decidedBy, body.note);
268
+ db.close();
269
+ console.log(`[Approvals] Approved request ${id} by ${decidedBy}`);
270
+ return c.json({
271
+ status: 'approved',
272
+ request_id: request.id,
273
+ approved_by: request.decided_by,
274
+ note: request.decision_note ?? null,
275
+ approved_at: request.decided_at,
276
+ });
277
+ }
278
+ catch (err) {
279
+ const message = err instanceof Error ? err.message : String(err);
280
+ console.error(`[Approvals] Error approving ${id}:`, message);
281
+ if (message.includes('not found'))
282
+ return c.json({ error: message }, 404);
283
+ if (message.includes('already'))
284
+ return c.json({ error: message }, 409);
285
+ return c.json({ error: 'Failed to approve request' }, 500);
286
+ }
287
+ });
288
+ /**
289
+ * POST /approvals/:id/deny
290
+ * Deny a pending request. Body: { decided_by: string, note?: string }
291
+ */
292
+ app.post('/approvals/:id/deny', async (c) => {
293
+ const id = c.req.param('id');
294
+ try {
295
+ const body = await c.req.json();
296
+ const decidedBy = body.decided_by || 'api';
297
+ const db = new core_1.OpenCardDatabase();
298
+ const request = db.denyRequest(id, decidedBy, body.note);
299
+ db.close();
300
+ console.log(`[Approvals] Denied request ${id} by ${decidedBy}`);
301
+ return c.json({
302
+ status: 'denied',
303
+ request_id: request.id,
304
+ denied_by: request.decided_by,
305
+ note: request.decision_note ?? null,
306
+ denied_at: request.decided_at,
307
+ });
308
+ }
309
+ catch (err) {
310
+ const message = err instanceof Error ? err.message : String(err);
311
+ console.error(`[Approvals] Error denying ${id}:`, message);
312
+ if (message.includes('not found'))
313
+ return c.json({ error: message }, 404);
314
+ if (message.includes('already'))
315
+ return c.json({ error: message }, 409);
316
+ return c.json({ error: 'Failed to deny request' }, 500);
317
+ }
318
+ });
319
+ // ─── Initialize Database ─────────────────────────────────────────────────────
320
+ /**
321
+ * Initialize the SQLite database and wire it into the reconciler.
322
+ * This runs at startup, before the server begins listening.
323
+ * If DB init fails, we log a warning and continue without persistence —
324
+ * the auth decision path must never be blocked by DB availability.
325
+ */
326
+ try {
327
+ const db = (0, core_1.initDatabase)();
328
+ const reconciler = new core_1.TransactionReconciler(db);
329
+ (0, core_1.setReconciler)(reconciler);
330
+ console.log('[WebhookServer] SQLite database initialized and reconciler wired ✓');
331
+ }
332
+ catch (err) {
333
+ console.error('[WebhookServer] ⚠ Failed to initialize SQLite database — webhook events will NOT be persisted.');
334
+ console.error('[WebhookServer] ⚠ Auth decisions are unaffected. Fix the DB issue to restore audit trail.');
335
+ console.error('[WebhookServer] DB init error:', err instanceof Error ? err.message : String(err));
336
+ }
337
+ // ─── Export app creation and server startup functions ─────────────────────────
338
+ /**
339
+ * Create and return the Hono webhook application.
340
+ * This is exported so the serve command can reuse the same app.
341
+ */
342
+ function createWebhookApp() {
343
+ return app;
344
+ }
345
+ /**
346
+ * Start the webhook server on the specified port.
347
+ * Returns a promise that resolves when the server is ready.
348
+ */
349
+ function startWebhookServer(port) {
350
+ return new Promise((resolve, reject) => {
351
+ // ── Validate and initialize Stripe ──────────────────────────────────────
352
+ const stripeKey = process.env.STRIPE_SECRET_KEY;
353
+ if (!stripeKey) {
354
+ console.error('[WebhookServer] FATAL: STRIPE_SECRET_KEY environment variable is not set.');
355
+ console.error('[WebhookServer] Set it with: export STRIPE_SECRET_KEY=sk_test_...');
356
+ reject(new Error('STRIPE_SECRET_KEY not set'));
357
+ return;
358
+ }
359
+ if (!stripe) {
360
+ stripe = new stripe_1.default(stripeKey, {
361
+ apiVersion: '2023-10-16',
362
+ });
363
+ }
364
+ const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET;
365
+ if (!webhookSecret) {
366
+ console.warn('[WebhookServer] ⚠ WARNING: STRIPE_WEBHOOK_SECRET not set — signature verification DISABLED');
367
+ console.warn('[WebhookServer] ⚠ This is insecure. Only acceptable for local development.');
368
+ console.warn('[WebhookServer] ⚠ Get your secret from: stripe listen --forward-to localhost:3000/webhooks/stripe');
369
+ }
370
+ // ── Start server ────────────────────────────────────────────────────────
371
+ console.log('[WebhookServer] OpenCard webhook server starting...');
372
+ console.log(`[WebhookServer] Stripe signature verification: ${webhookSecret ? 'ENABLED ✓' : 'DISABLED ⚠'}`);
373
+ (0, node_server_1.serve)({
374
+ fetch: app.fetch,
375
+ port: port,
376
+ }, (info) => {
377
+ console.log(`[WebhookServer] Server listening on port ${info.port}`);
378
+ console.log(`[WebhookServer] Webhook endpoint: POST http://localhost:${info.port}/webhooks/stripe`);
379
+ console.log(`[WebhookServer] Health check: GET http://localhost:${info.port}/health`);
380
+ console.log('[WebhookServer] Ready to handle Stripe events.');
381
+ resolve();
382
+ });
383
+ });
384
+ }
385
+ // ─── Auto-start when run directly ──────────────────────────────────────────────
386
+ // Note: This is now a library module imported by the CLI.
387
+ // The CLI uses startWebhookServer() to control when the server starts.
388
+ // Auto-start is no longer supported from the library itself.
389
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@opencard-dev/webhook-server",
3
+ "version": "0.1.0",
4
+ "description": "Hono-based webhook server for real-time Stripe Issuing authorization decisions",
5
+ "main": "dist/index.js",
6
+ "scripts": {
7
+ "build": "tsc",
8
+ "type-check": "tsc --noEmit",
9
+ "dev": "tsc --watch",
10
+ "start": "node dist/index.js",
11
+ "test": "vitest run"
12
+ },
13
+ "dependencies": {
14
+ "@hono/node-server": "^1.13.8",
15
+ "@opencard-dev/core": "workspace:*",
16
+ "hono": "^4.7.7",
17
+ "stripe": "^14.0.0"
18
+ },
19
+ "devDependencies": {
20
+ "@types/node": "^20.0.0",
21
+ "typescript": "^5.3.0",
22
+ "vitest": "^1.0.0"
23
+ },
24
+ "files": [
25
+ "dist"
26
+ ],
27
+ "keywords": [
28
+ "webhook",
29
+ "stripe",
30
+ "hono",
31
+ "opencard"
32
+ ],
33
+ "author": "Longshot Design",
34
+ "license": "MIT",
35
+ "repository": {
36
+ "type": "git",
37
+ "url": "https://github.com/JLongshot/opencard.git",
38
+ "directory": "packages/webhook-server"
39
+ }
40
+ }