@neo-edi/sdk 1.0.20 → 1.0.21

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.
package/README.md CHANGED
@@ -267,6 +267,94 @@ const data = await client.graphql.query<{ edi852Headers: Edi852Header[] }>(`
267
267
  `, { filter: { vendorPartnerId: 'VENDOR-001' } });
268
268
  ```
269
269
 
270
+ ## Webhooks
271
+
272
+ Register endpoints that receive HMAC-signed HTTP callbacks when events happen for a
273
+ trading partner — e.g. `document.ingested` when a delivery is ingested — so you don't
274
+ have to poll.
275
+
276
+ ```typescript
277
+ // Create (subscribe) an endpoint.
278
+ // The returned signingSecret is shown ONCE — store it now.
279
+ const endpoint = await client.webhooks.create({
280
+ partnerId: 'PARTNER-001',
281
+ url: 'https://your-app.com/hooks/neo-edi',
282
+ description: 'Production receiver',
283
+ eventTypes: ['document.ingested'] // omit to subscribe to all events
284
+ });
285
+ console.log(endpoint.signingSecret); // whsec_... — persist this securely
286
+
287
+ // List endpoints for a partner
288
+ const endpoints = await client.webhooks.list('PARTNER-001');
289
+
290
+ // Get one endpoint
291
+ const one = await client.webhooks.get(endpoint.id);
292
+
293
+ // Update — change the URL, events, or pause delivery with isActive: false
294
+ await client.webhooks.update(endpoint.id, {
295
+ url: 'https://your-app.com/hooks/neo-edi/v2',
296
+ isActive: false
297
+ });
298
+
299
+ // Delete (unsubscribe)
300
+ await client.webhooks.delete(endpoint.id);
301
+
302
+ // Inspect recent delivery attempts (audit / debugging)
303
+ const deliveries = await client.webhooks.listDeliveries(endpoint.id, { limit: 20 });
304
+ for (const d of deliveries) {
305
+ console.log(d.status, d.responseStatus, d.attemptCount);
306
+ }
307
+ ```
308
+
309
+ ### Receiving & verifying deliveries
310
+
311
+ Each delivery is an HTTP `POST` to your URL with a JSON envelope
312
+ (`{ id, type, partnerId, createdAt, data }`) and these headers:
313
+
314
+ | Header | Example | Purpose |
315
+ |--------|---------|---------|
316
+ | `X-Webhook-Timestamp` | `1717416000` | Unix seconds; part of the signed content |
317
+ | `X-Webhook-Signature` | `t=1717416000,v1=9f86d08...` | `v1` is the HMAC-SHA256 hex digest |
318
+
319
+ Verify by recomputing `HMAC_SHA256(signingSecret, "{timestamp}.{rawBody}")` over the
320
+ **raw** body (before JSON parsing) and comparing it to the `v1=` value. Example with
321
+ Express:
322
+
323
+ ```typescript
324
+ import crypto from 'node:crypto';
325
+ import express from 'express';
326
+
327
+ const app = express();
328
+
329
+ // Capture the raw body — the signature is computed over the exact bytes sent.
330
+ app.post(
331
+ '/hooks/neo-edi',
332
+ express.raw({ type: 'application/json' }),
333
+ (req, res) => {
334
+ const sig = req.header('X-Webhook-Signature') ?? '';
335
+ const parts = Object.fromEntries(sig.split(',').map((kv) => kv.split('=')));
336
+ const rawBody = req.body.toString('utf8');
337
+
338
+ const expected = crypto
339
+ .createHmac('sha256', process.env.WEBHOOK_SECRET!)
340
+ .update(`${parts.t}.${rawBody}`)
341
+ .digest('hex');
342
+
343
+ const valid =
344
+ parts.v1?.length === expected.length &&
345
+ crypto.timingSafeEqual(Buffer.from(parts.v1), Buffer.from(expected));
346
+
347
+ if (!valid) return res.status(401).send('bad signature');
348
+
349
+ const event = JSON.parse(rawBody);
350
+ // Dedupe on event.id — it is stable across retries.
351
+ // ... handle event.data ...
352
+
353
+ res.sendStatus(200); // any 2xx acknowledges; otherwise it's retried (up to 3x)
354
+ }
355
+ );
356
+ ```
357
+
270
358
  ## Error Handling
271
359
 
272
360
  ```typescript
package/dist/index.js CHANGED
@@ -63,7 +63,7 @@ var NeoEdiValidationError = class extends NeoEdiError {
63
63
  // package.json
64
64
  var package_default = {
65
65
  name: "@neo-edi/sdk",
66
- version: "1.0.20",
66
+ version: "1.0.21",
67
67
  description: "TypeScript SDK for the Neo-EDI platform",
68
68
  main: "./dist/index.js",
69
69
  module: "./dist/index.mjs",
package/dist/index.mjs CHANGED
@@ -32,7 +32,7 @@ var NeoEdiValidationError = class extends NeoEdiError {
32
32
  // package.json
33
33
  var package_default = {
34
34
  name: "@neo-edi/sdk",
35
- version: "1.0.20",
35
+ version: "1.0.21",
36
36
  description: "TypeScript SDK for the Neo-EDI platform",
37
37
  main: "./dist/index.js",
38
38
  module: "./dist/index.mjs",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neo-edi/sdk",
3
- "version": "1.0.20",
3
+ "version": "1.0.21",
4
4
  "description": "TypeScript SDK for the Neo-EDI platform",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -16,7 +16,7 @@
16
16
  "dist"
17
17
  ],
18
18
  "dependencies": {
19
- "@neo-edi/types": "1.0.20"
19
+ "@neo-edi/types": "1.0.21"
20
20
  },
21
21
  "devDependencies": {
22
22
  "@types/node": "^20",
@@ -24,7 +24,7 @@
24
24
  "typescript": "^5.0.0"
25
25
  },
26
26
  "peerDependencies": {
27
- "@neo-edi/types": "1.0.20"
27
+ "@neo-edi/types": "1.0.21"
28
28
  },
29
29
  "scripts": {
30
30
  "build": "tsup src/index.ts --format cjs,esm --dts",