@neetru/sdk 2.3.5 → 3.0.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.
Files changed (83) hide show
  1. package/CHANGELOG.md +184 -0
  2. package/README.md +59 -22
  3. package/dist/auth.cjs +368 -142
  4. package/dist/auth.cjs.map +1 -1
  5. package/dist/auth.d.cts +1 -1
  6. package/dist/auth.d.ts +1 -1
  7. package/dist/auth.mjs +368 -142
  8. package/dist/auth.mjs.map +1 -1
  9. package/dist/catalog.cjs +21 -2
  10. package/dist/catalog.cjs.map +1 -1
  11. package/dist/catalog.d.cts +1 -1
  12. package/dist/catalog.d.ts +1 -1
  13. package/dist/catalog.mjs +21 -2
  14. package/dist/catalog.mjs.map +1 -1
  15. package/dist/checkout.cjs +21 -2
  16. package/dist/checkout.cjs.map +1 -1
  17. package/dist/checkout.d.cts +1 -1
  18. package/dist/checkout.d.ts +1 -1
  19. package/dist/checkout.mjs +21 -2
  20. package/dist/checkout.mjs.map +1 -1
  21. package/dist/db.cjs +36 -5
  22. package/dist/db.cjs.map +1 -1
  23. package/dist/db.d.cts +1 -1
  24. package/dist/db.d.ts +1 -1
  25. package/dist/db.mjs +36 -5
  26. package/dist/db.mjs.map +1 -1
  27. package/dist/entitlements.cjs +21 -2
  28. package/dist/entitlements.cjs.map +1 -1
  29. package/dist/entitlements.d.cts +1 -1
  30. package/dist/entitlements.d.ts +1 -1
  31. package/dist/entitlements.mjs +21 -2
  32. package/dist/entitlements.mjs.map +1 -1
  33. package/dist/errors.cjs.map +1 -1
  34. package/dist/errors.d.cts +3 -1
  35. package/dist/errors.d.ts +3 -1
  36. package/dist/errors.mjs.map +1 -1
  37. package/dist/index.cjs +432 -152
  38. package/dist/index.cjs.map +1 -1
  39. package/dist/index.d.cts +31 -19
  40. package/dist/index.d.ts +31 -19
  41. package/dist/index.mjs +432 -152
  42. package/dist/index.mjs.map +1 -1
  43. package/dist/mocks.cjs +49 -37
  44. package/dist/mocks.cjs.map +1 -1
  45. package/dist/mocks.d.cts +23 -19
  46. package/dist/mocks.d.ts +23 -19
  47. package/dist/mocks.mjs +49 -37
  48. package/dist/mocks.mjs.map +1 -1
  49. package/dist/notifications.cjs +80 -14
  50. package/dist/notifications.cjs.map +1 -1
  51. package/dist/notifications.d.cts +1 -1
  52. package/dist/notifications.d.ts +1 -1
  53. package/dist/notifications.mjs +80 -14
  54. package/dist/notifications.mjs.map +1 -1
  55. package/dist/react.d.cts +1 -1
  56. package/dist/react.d.ts +1 -1
  57. package/dist/support.cjs +72 -12
  58. package/dist/support.cjs.map +1 -1
  59. package/dist/support.d.cts +1 -1
  60. package/dist/support.d.ts +1 -1
  61. package/dist/support.mjs +72 -12
  62. package/dist/support.mjs.map +1 -1
  63. package/dist/telemetry.cjs +23 -4
  64. package/dist/telemetry.cjs.map +1 -1
  65. package/dist/telemetry.d.cts +1 -1
  66. package/dist/telemetry.d.ts +1 -1
  67. package/dist/telemetry.mjs +23 -4
  68. package/dist/telemetry.mjs.map +1 -1
  69. package/dist/{types-sGGN1vkg.d.cts → types-DLOvkeAP.d.cts} +110 -49
  70. package/dist/{types-CSC-RIdS.d.ts → types-dw19bdID.d.ts} +110 -49
  71. package/dist/usage.cjs +57 -63
  72. package/dist/usage.cjs.map +1 -1
  73. package/dist/usage.d.cts +1 -1
  74. package/dist/usage.d.ts +1 -1
  75. package/dist/usage.mjs +57 -63
  76. package/dist/usage.mjs.map +1 -1
  77. package/dist/webhooks.cjs +110 -33
  78. package/dist/webhooks.cjs.map +1 -1
  79. package/dist/webhooks.d.cts +1 -1
  80. package/dist/webhooks.d.ts +1 -1
  81. package/dist/webhooks.mjs +110 -33
  82. package/dist/webhooks.mjs.map +1 -1
  83. package/package.json +2 -2
package/CHANGELOG.md CHANGED
@@ -5,6 +5,190 @@ All notable changes to `@neetru/sdk` will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [3.0.0] - 2026-05-27 — **MAJOR: contract hardening + Universal runtime + paths canonical**
9
+
10
+ > Major rewrite resolvendo 5 CRITICAL + 6 HIGH do review `CHEFE_SDK_REVIEW_2026-05-27`.
11
+ > SDK 2.3.6 estava com **4 dos 11 namespaces broken** contra Core LIVE (`usage`, `webhooks`, `notifications`, `support`) — 13 endpoints batendo 404/400. Tests passavam validando contrato errado (mock fetch aceita qualquer URL).
12
+ >
13
+ > **Justificativa do MAJOR**: paths/body shapes mudaram em 4 namespaces; 2 métodos removidos (`usage.track`, `usage.getQuota`); `verifyWebhookSignature` virou async. Toda CLI release/SaaS inaugural depende disso primeiro.
14
+
15
+ ### BREAKING CHANGES
16
+
17
+ #### 1. `usage` namespace 100% rewrite (CRITICAL-SDK-01)
18
+
19
+ - **REMOVIDO** `usage.track(event, props)` — Core não aceitava esse shape; era vaporware testado contra mock. Use `usage.report(resource, qty, opts)`.
20
+ - **REMOVIDO** `usage.getQuota(metric)` — endpoint `/usage/quota` nunca existiu no Core. `usage.report()` devolve `{value, limit, remaining}` inline em todo POST.
21
+ - Paths corrigidos:
22
+ - `/sdk/v1/usage/record` → `/api/sdk/v1/usage/record` (canonical)
23
+ - `/sdk/v1/entitlements` → `/api/sdk/v1/entitlements` (canonical)
24
+ - `usage.report()` agora envia `Idempotency-Key: <uuid v4>` header automaticamente.
25
+ - `usage.check()` retorna novo field `behavior: "readonly" | null` (inferido de `reason === "limit_exceeded"`).
26
+
27
+ #### 2. `webhooks` namespace — `productId` obrigatório (CRITICAL-SDK-03)
28
+
29
+ - `RegisterWebhookInput.productId` adicionado. Default resolve do `config.productId`; lança `validation_failed` se ambos ausentes.
30
+ - `list(options?)`, `unregister(id, options?)`, `test(id, options?)` agora aceitam `{ productId }` (Core exige query param em todos os verbos).
31
+ - `webhooks.test()` envia `Idempotency-Key` header.
32
+
33
+ #### 3. `notifications` namespace — `productId` obrigatório (CRITICAL-SDK-04)
34
+
35
+ - `SendNotificationInput.productId` adicionado (Core valida via Zod `SendNotificationSchema`).
36
+ - `ListNotificationsOptions.productId` adicionado.
37
+ - `NotificationScopeOptions` novo type para `markRead`/`dismiss`.
38
+ - `notifications.send()` envia `Idempotency-Key` header.
39
+
40
+ #### 4. `support` namespace — endpoint canonical (CRITICAL-SDK-02)
41
+
42
+ - Endpoint mudou de `/api/v1/products/{slug}/tickets` (vaporware — **nunca existiu no Core**) para `/api/sdk/v1/support/tickets` (canonical SDK path).
43
+ - `productId` obrigatório (default `config.productId`).
44
+ - `createTicket()` envia `Idempotency-Key` header.
45
+ - `listMyTickets(options?)` aceita `{ productSlug }` override.
46
+ - **Pré-requisito Core**: endpoint `/api/sdk/v1/support/tickets` precisa ser construído (vide `docs/CORE_CHANGES_REQUIRED.md` §1).
47
+
48
+ #### 5. `verifyWebhookSignature` é async + Universal (HIGH-SDK-03)
49
+
50
+ - **Antes (2.x)**: `(payload, sig, secret) => boolean` — usava `require('node:crypto')` que quebrava em bundle browser/Edge ESM puro. README anunciava "Universal" mas mentia.
51
+ - **Agora (3.0)**: `(payload, sig, secret) => Promise<boolean>` via `WebCrypto.subtle.sign` HMAC-SHA-256 + constant-time XOR compare.
52
+ - Funciona em Node ≥20, browsers modernos, Vercel Edge Functions, Cloudflare Workers.
53
+ - Lança `NeetruError('runtime_unsupported')` se WebCrypto ausente (distingue "ambiente não suporta" de "assinatura inválida").
54
+
55
+ #### 6. `telemetry.event` / `telemetry.track` path canonical (CRITICAL-SDK-05)
56
+
57
+ - `POST /api/v1/sdk/telemetry/event` → `POST /api/sdk/v1/telemetry/event` (canonical SDK pattern).
58
+ - Pré-requisito Core: alias/handler para o canonical path (vide `docs/CORE_CHANGES_REQUIRED.md` §3).
59
+
60
+ #### 7. `baseUrl` allowlist fail-closed (HIGH-SDK-05)
61
+
62
+ - `createNeetruClient` agora chama `validateBaseUrl` antes de aceitar `config.baseUrl`.
63
+ - Allowlist padrão: HTTPS `*.neetru.com` + `localhost`/`127.0.0.1`/`*.test`/`*.localhost`.
64
+ - `NeetruClientConfig.allowedHosts: string[]` novo — adicione hosts custom (suffix match).
65
+ - Bypass: `NEETRU_ALLOW_INSECURE_BASEURL=1` (env) ou `globalThis.NEETRU_ALLOW_INSECURE_BASEURL='1'`. Warning no console quando ativo.
66
+ - Cenário ataque fechado: dev configura `NEETRU_BASE_URL=http://attacker.example/` no `.env`, agora lança `invalid_config` em vez de vazar Bearer.
67
+
68
+ #### 8. Retries default por método (HIGH-SDK-01)
69
+
70
+ - **GET/HEAD**: 2 retries default (= 3 tentativas) — idempotente, seguro.
71
+ - **POST/PUT/PATCH/DELETE**: **0 retries default** — mutations podem duplicar side effects (ex: `FieldValue.increment` no `usage/record`).
72
+ - Caller pode override: `httpRequest(config, { method: 'POST', ..., retries: 2 })`.
73
+ - Combinado com `Idempotency-Key` automático em mutations: dup-write rate → zero.
74
+
75
+ #### 9. Cleanup de stubs deprecated
76
+
77
+ - `initNeetru` (deprecated desde v0.2, no-op em v2.0) **definitivamente removido**.
78
+ - `NeetruConfig` type alias **removido** (use `NeetruClientConfig`).
79
+
80
+ #### 10. `NeetruErrorCode` ganhou `runtime_unsupported`
81
+
82
+ - Lançado por `verifyWebhookSignature` quando WebCrypto ausente. Não é breaking se você usa só `instanceof NeetruError`; é breaking se você fez `switch` no union type (TS error em case missing).
83
+
84
+ ### ADDED
85
+
86
+ - `src/idempotency.ts` — `generateIdempotencyKey()` via `crypto.randomUUID()` (Node ≥20, browsers, Edge).
87
+ - `src/base-url-allowlist.ts` — `validateBaseUrl(url, allowedHosts)` fail-closed.
88
+ - `HttpRequestOptions.idempotencyKey: boolean | string` — injeta header automaticamente em todo POST de mutation.
89
+ - `client.usage.check().behavior: 'readonly' | null` — sinal para `<EntitlementGate mode='readonly'>` decidir UI.
90
+ - `NeetruClientConfig.allowedHosts` — extension point para hosts custom (staging/proxies).
91
+ - `MockUsage.__getReports()` substitui `__getRecords()` (legacy alias mantido).
92
+ - Test suite: `src/__tests__/base-url-allowlist.test.ts` (16 tests), `src/__tests__/idempotency.test.ts` (9 tests).
93
+ - `docs/CORE_CHANGES_REQUIRED.md` — catálogo de 5 endpoints Core a criar/modificar para 3.0 funcionar end-to-end.
94
+
95
+ ### FIXED
96
+
97
+ - `<EntitlementGate>` v1.1 não morre mais em silent `allowed:false` — paths corrigidos no `usage.check` (era dependência transitiva, fixed via CRITICAL-SDK-01).
98
+ - README v2.x prometia "Universal: browser/Node/Edge" mas `verifyWebhookSignature` só funcionava em Node. 3.0 cumpre.
99
+
100
+ ### Migration guide v2.x → v3.0
101
+
102
+ ```ts
103
+ // ❌ Antes (v2.x)
104
+ await client.usage.track('api_call', { route: '/x' });
105
+ const quota = await client.usage.getQuota('api_calls');
106
+
107
+ await client.webhooks.register({
108
+ url: 'https://x.com/h',
109
+ events: ['subscription.activated'],
110
+ });
111
+
112
+ await client.notifications.send({
113
+ userId: 'u1',
114
+ kind: 'order.received',
115
+ title: 'Pedido',
116
+ });
117
+
118
+ await client.support.createTicket({
119
+ subject: 'help',
120
+ message: 'x',
121
+ });
122
+
123
+ // ✅ Depois (v3.0)
124
+ const client = createNeetruClient({
125
+ apiKey: 'nrt_...',
126
+ productId: 'gestovendas', // 🆕 default p/ todos os namespaces
127
+ tenantId: 't-acme', // 🆕 default p/ usage
128
+ });
129
+
130
+ const result = await client.usage.report('api_call', 1);
131
+ // result.value, result.limit, result.remaining inline — não precisa getQuota separado.
132
+
133
+ const allowed = await client.usage.check('api_call');
134
+ // allowed.behavior === 'readonly' quando over limit (use em <EntitlementGate>)
135
+
136
+ await client.webhooks.register({
137
+ // productId default config.productId, ou explícito:
138
+ url: 'https://x.com/h',
139
+ events: ['subscription.activated'],
140
+ });
141
+
142
+ await client.notifications.send({
143
+ userId: 'u1',
144
+ kind: 'order.received',
145
+ title: 'Pedido',
146
+ });
147
+
148
+ await client.support.createTicket({
149
+ subject: 'help',
150
+ message: 'x',
151
+ });
152
+ ```
153
+
154
+ ### Verificação de assinatura de webhook — agora async
155
+
156
+ ```ts
157
+ // ❌ Antes (v2.x) — sync, quebrava em Edge
158
+ import { verifyWebhookSignature } from '@neetru/sdk/webhooks';
159
+ const ok = verifyWebhookSignature(rawPayload, sigHeader, secret);
160
+
161
+ // ✅ Depois (v3.0) — async, Universal
162
+ const ok = await verifyWebhookSignature(rawPayload, sigHeader, secret);
163
+ ```
164
+
165
+ ### Endpoints Core que precisam ser criados/modificados antes do publish 3.0
166
+
167
+ Vide `docs/CORE_CHANGES_REQUIRED.md` completo:
168
+
169
+ 1. **CRITICAL** — `POST/GET /api/sdk/v1/support/tickets` (customer-facing) — 3-4h.
170
+ 2. **HIGH** — Idempotency-Key dedup em endpoints de mutation — 4-6h.
171
+ 3. **HIGH** — `POST /api/sdk/v1/telemetry/event` (canonical) — 1-2h ou alias middleware.
172
+ 4. **LOW** — `behavior` field em `/api/sdk/v1/entitlements` response — 30min.
173
+ 5. **LOW** — Verificar envelope `/api/sdk/v1/datastore/{collection}` — 30min check.
174
+
175
+ ### Tests
176
+
177
+ - **719 tests passing** (era 668 em 2.3.6). Tsc clean. Build clean.
178
+ - Contract tests reescritos para validar paths canonical + body shapes + headers (Idempotency-Key UUID v4 regex match).
179
+
180
+ ## [2.3.6] - 2026-05-26 — **fix(bundle): webpackIgnore em TODOS os dynamic imports node-only — bug_168368ae [CRITICAL]**
181
+
182
+ ### Corrigido
183
+
184
+ - **bug_168368aedb664edba1ca1f1945b568ac (CRITICAL) — fix 2.3.5 foi parcial: `webpackIgnore` aplicado só em `createSqlClientFromConfig`, mas `createPgPool` + `createDrizzleHandle` (helpers separados no mesmo arquivo) + os 3 dynamic imports em `MockDb.sql()` (`pg-mem`, `drizzle-orm/node-postgres`, `@electric-sql/pglite`, `drizzle-orm/pglite`) ainda estavam sem o magic comment.**
185
+ Resultado: webpack do consumer Next.js seguia o module graph daqueles paths e ainda tentava bundle `pg` → `Module not found: tls/fs` no client.
186
+ Fix 2.3.6: `/* webpackIgnore: true */` em **todos** os 7 spots de `await import('pg' | 'drizzle-orm/node-postgres' | 'pg-mem' | '@electric-sql/pglite' | 'drizzle-orm/pglite')` em `sql-client.ts` + `mocks.ts`.
187
+
188
+ ### Testes (antirregressão atualizado)
189
+
190
+ - **`no-node-deps-in-browser-bundle.regression.test.ts`** expandido: agora pega `import("X")` dynamic SEM `webpackIgnore` (regex negative lookahead). Antes só cobria `from "X"` static. Adicionado 4 patterns: pg, drizzle-orm/node-postgres, pg-mem, @electric-sql/pglite.
191
+
8
192
  ## [2.3.5] - 2026-05-26 — **fix(bundle): node deps fora do browser entry — bug_0f836014 [CRITICAL]**
9
193
 
10
194
  ### Corrigido
package/README.md CHANGED
@@ -1,62 +1,79 @@
1
1
  # @neetru/sdk
2
2
 
3
- > Biblioteca runtime oficial pra consumir o ecossistema Neetru a partir de produtos SaaS (Next.js, Node, browser, Edge runtimes).
3
+ > Biblioteca runtime oficial pra consumir o ecossistema Neetru a partir de produtos SaaS (Next.js, Node ≥20, browser, Edge runtimes).
4
4
 
5
5
  <p>
6
6
  <img alt="npm" src="https://img.shields.io/npm/v/@neetru/sdk?logo=npm">
7
7
  <img alt="downloads" src="https://img.shields.io/npm/dm/@neetru/sdk?logo=npm">
8
8
  <img alt="bundle size" src="https://img.shields.io/bundlephobia/minzip/@neetru/sdk">
9
- <img alt="Node" src="https://img.shields.io/badge/node-%3E%3D18-339933?logo=node.js&logoColor=white">
9
+ <img alt="Node" src="https://img.shields.io/badge/node-%3E%3D20-339933?logo=node.js&logoColor=white">
10
10
  <img alt="license" src="https://img.shields.io/badge/license-MIT-22c55e">
11
11
  </p>
12
12
 
13
+ > ## ⚠ 3.0 é breaking — leia o [CHANGELOG](./CHANGELOG.md) antes de upgrade
14
+ >
15
+ > SDK 2.x estava com 4 namespaces broken contra Core LIVE. 3.0 corrige paths canonical, exige `productId` em mutations, adiciona `Idempotency-Key` automático, `baseUrl` allowlist fail-closed, e `verifyWebhookSignature` virou Universal async via WebCrypto.
16
+ >
17
+ > **Métodos removidos**: `usage.track`, `usage.getQuota` (use `usage.report` / `usage.check`).
18
+ >
19
+ > **Migration guide completo no CHANGELOG.md**.
20
+
13
21
  ## Instalação
14
22
 
15
23
  ```bash
16
24
  npm install @neetru/sdk
17
25
  ```
18
26
 
19
- ## Hello world
27
+ ## Hello world (3.0)
20
28
 
21
29
  ```ts
22
30
  import { createNeetruClient } from '@neetru/sdk';
23
31
 
24
32
  const neetru = createNeetruClient({
25
33
  apiKey: process.env.NEETRU_API_KEY, // nrt_<keyId>_<secret>
34
+ productId: 'gestovendas', // 3.0: default p/ todos namespaces
35
+ tenantId: 't-acme', // 3.0: default p/ usage
26
36
  env: 'prod', // 'dev' = mocks in-memory
27
37
  });
28
38
 
29
39
  // Auth
30
40
  const user = await neetru.auth.signIn();
31
- console.log('logado como:', user?.email);
32
41
 
33
- // Entitlement check
34
- const canAi = await neetru.entitlements.check('gestovendas', 'ai_recommendations');
35
- if (canAi) { /* mostrar feature */ }
42
+ // Meter consumo (canonical /api/sdk/v1/usage/record)
43
+ const r = await neetru.usage.report('api_call', 1);
44
+ console.log(r.value, '/', r.limit, '— restam', r.remaining);
45
+
46
+ // Entitlement check com behavior=readonly
47
+ const check = await neetru.usage.check('api_call');
48
+ if (!check.allowed && check.behavior === 'readonly') {
49
+ // UI degradada — read-only sem quebrar engajamento
50
+ }
36
51
  ```
37
52
 
38
53
  ## Princípios
39
54
 
40
55
  - **Vendor-neutral** — superfície pública NÃO vaza Firebase/Stripe/etc. Backend Neetru pode mudar no futuro sem reescrever produto.
41
56
  - **Tree-shakable** — ESM-first, sem side-effects. Importe só os namespaces que usa.
42
- - **Universal** — browser, Node ≥18, Edge runtimes (Vercel Edge, Cloudflare Workers). Usa `fetch` global.
57
+ - **Universal** — browser, Node ≥20, Edge runtimes (Vercel Edge, Cloudflare Workers). Usa `fetch` global + WebCrypto.
58
+ - **Fail-closed segurança** — `baseUrl` allowlist (`*.neetru.com` + localhost) rejeita phishing/MITM. Bypass: `NEETRU_ALLOW_INSECURE_BASEURL=1`.
59
+ - **Idempotency-Key automático** — mutations (`usage.report`, `webhooks.test`, `notifications.send`, `support.createTicket`) injetam UUID v4 header. `retries: 0` default em POSTs para defesa em camada.
43
60
  - **Tipado** — erros via `NeetruError` com `.code`, `.status`, `.requestId`.
44
61
  - **Dev mode** — `NEETRU_ENV=dev` ativa mocks automáticos — zero rede em testes locais.
45
62
 
46
- ## Namespaces v1.2
47
-
48
- | Namespace | O que oferece |
49
- |---|---|
50
- | `auth` | OIDC sign-in / sign-out + dev fixture user |
51
- | `catalog` | Produtos públicos (`list` / `get`) |
52
- | `entitlements` | Verificação `(productSlug, feature)` boolean ou detalhado |
53
- | `telemetry` | `event(...)` + `log(...)` per-product |
54
- | `usage` | `track` / `getQuota` / `report` / `check` (metering canônico) |
55
- | `support` | `createTicket` / `listMyTickets` |
56
- | `db` | Coleções tenant-scoped (`list` / `get` / `set` / `add` / `update` / `remove`) |
57
- | `checkout` | Stripe Checkout intent (`start` / `get` / `cancel` + auto-redirect) |
58
- | `webhooks` ⭐ v1.2 | Produtos registram URL pra receber eventos Core (subscription.activated, usage.quota_exceeded, etc) |
59
- | `notifications` ⭐ v1.2 | Produto envia notification in-app pros SEUS usuários (distinto das staff-only do Core) |
63
+ ## Namespaces 3.0
64
+
65
+ | Namespace | O que oferece | Endpoint |
66
+ |---|---|---|
67
+ | `auth` | OIDC sign-in/out + verifyToken | `auth.neetru.com/api/v1/oauth/*` |
68
+ | `catalog` | Produtos públicos (`list` / `get`) | `/api/sdk/v1/catalog` |
69
+ | `entitlements` | Verificação boolean simples (legacy compat) | `/api/v1/sdk/entitlements/check` |
70
+ | `telemetry` | `event(...)` + `track(...)` + `log(...)` | `/api/sdk/v1/telemetry/{event,log}` |
71
+ | `usage` | `report` / `check` (metering + entitlement full) | `/api/sdk/v1/usage/record`, `/api/sdk/v1/entitlements` |
72
+ | `support` | `createTicket` / `listMyTickets` | `/api/sdk/v1/support/tickets` ⭐ 3.0 |
73
+ | `db` | Coleções tenant-scoped (offline-first v2.0) | `/api/sdk/v1/datastore/*` |
74
+ | `checkout` | Stripe Checkout intent | `/api/v1/checkout/intents` |
75
+ | `webhooks` | Produtos registram URL pra receber eventos | `/api/sdk/v1/webhooks` |
76
+ | `notifications` | Produto envia notification in-app | `/api/sdk/v1/notifications` |
60
77
 
61
78
  ## Exemplos por namespace
62
79
 
@@ -78,6 +95,26 @@ Eventos recebidos no seu endpoint chegam com:
78
95
  - `X-Neetru-Signature: sha256=<hmac>` (se `secret` registrado)
79
96
  - `X-Neetru-Timestamp: <ms>` (replay protection — rejeitar > 5min skew)
80
97
 
98
+ #### Verificar assinatura no consumer (3.0 — async + Universal)
99
+
100
+ ```ts
101
+ import { verifyWebhookSignature } from '@neetru/sdk';
102
+
103
+ // Next.js Route Handler (Edge OU Node runtime — funciona em ambos)
104
+ export async function POST(req: Request) {
105
+ const raw = await req.text();
106
+ const sig = req.headers.get('X-Neetru-Signature');
107
+ const ok = await verifyWebhookSignature(raw, sig, process.env.NEETRU_WEBHOOK_SECRET!);
108
+ if (!ok) return new Response('unauthorized', { status: 401 });
109
+
110
+ const event = JSON.parse(raw);
111
+ // ... handle event
112
+ return Response.json({ ok: true });
113
+ }
114
+ ```
115
+
116
+ > **Breaking 3.0**: era `boolean` sync usando `node:crypto`. Agora `Promise<boolean>` via WebCrypto.subtle — funciona em Cloudflare Workers, Vercel Edge, browsers e Node ≥20.
117
+
81
118
  ### Notifications produto → user (v1.2)
82
119
 
83
120
  ```ts