@wazapi/sdk 0.1.0 → 0.2.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.
package/README.md CHANGED
@@ -119,6 +119,52 @@ await wazapi.upsertContactByExternalId('customer-1847', {
119
119
  })
120
120
  ```
121
121
 
122
+ ## Webhooks
123
+
124
+ Wazapi POSTs events to the HTTPS endpoint you register in **Settings → Wazapi API**.
125
+ The SDK ships the payload types; the union is discriminated on `type`, so narrowing
126
+ `event.type` narrows `event.data` with it.
127
+
128
+ ```ts
129
+ import type { WazapiWebhookEvent } from '@wazapi/sdk'
130
+
131
+ function handle(event: WazapiWebhookEvent) {
132
+ switch (event.type) {
133
+ case 'message.received':
134
+ return reply(event.data.contact_uuid, event.data.content)
135
+ case 'message.status.updated':
136
+ return markDelivered(event.data.message_uuid, event.data.status)
137
+ case 'flow.execution.updated':
138
+ return event.data.error ? alert(event.data.error.code) : done()
139
+ }
140
+ }
141
+ ```
142
+
143
+ Verify the signature over the **raw** body, before parsing:
144
+
145
+ ```ts
146
+ import { createHmac, timingSafeEqual } from 'node:crypto'
147
+
148
+ function verify(rawBody: string, headers: Record<string, string>, secret: string) {
149
+ const expected = createHmac('sha256', secret)
150
+ .update(`${headers['wazapi-timestamp']}.${rawBody}`)
151
+ .digest('hex')
152
+ const received = (headers['wazapi-signature'] ?? '').replace(/^v1=/, '')
153
+ const a = Buffer.from(expected)
154
+ const b = Buffer.from(received)
155
+ return a.length === b.length && timingSafeEqual(a, b)
156
+ }
157
+ ```
158
+
159
+ Delivery is at-least-once — deduplicate on `event.id` (also sent as the
160
+ `Wazapi-Event-Id` header). Wazapi treats only `2xx` as success and retries after
161
+ 1min, 5min, 30min, 2h, 12h and 24h.
162
+
163
+ Two fields are added at delivery time when they apply: `data.external_id`, your own
164
+ identifier echoed back when the contact was linked via `upsertContactByExternalId`,
165
+ and `data.tracking`, the allowlisted attribution subset (`utm_*`, click IDs, ad
166
+ referral fields). No other custom field key is ever forwarded.
167
+
122
168
  ## Configuration
123
169
 
124
170
  ```ts
package/dist/types.d.ts CHANGED
@@ -143,3 +143,81 @@ export interface AcceptedResult {
143
143
  /** True when the request replayed a previously accepted idempotent operation. */
144
144
  replayed: boolean;
145
145
  }
146
+ export type WebhookEventType = 'message.received' | 'message.status.updated' | 'conversation.created' | 'conversation.updated' | 'flow.execution.updated' | 'webhook.test';
147
+ /**
148
+ * Allowlisted attribution subset of the contact's and conversation's custom
149
+ * fields. Conversation values win over contact values (last touch over first
150
+ * touch); no other custom field key is ever forwarded.
151
+ */
152
+ export type WebhookTracking = Record<string, unknown>;
153
+ /**
154
+ * Added at delivery time whenever the payload carries a `contact_uuid` and that
155
+ * contact was linked through `PUT /contacts/external/{external_id}`.
156
+ */
157
+ interface WebhookContactRefs {
158
+ external_id?: string;
159
+ tracking?: WebhookTracking;
160
+ }
161
+ export interface MessageReceivedData extends WebhookContactRefs {
162
+ message_uuid: string;
163
+ conversation_uuid: string;
164
+ contact_uuid: string;
165
+ channel_uuid: string;
166
+ type: string;
167
+ content: Record<string, unknown>;
168
+ status: string;
169
+ }
170
+ /**
171
+ * `provider_message_id` comes from the provider status callback; `operation_uuid`
172
+ * and `conversation_uuid` are present when the message was sent through the API.
173
+ */
174
+ export interface MessageStatusUpdatedData {
175
+ message_uuid: string;
176
+ status: string;
177
+ provider_message_id?: string | null;
178
+ operation_uuid?: string;
179
+ conversation_uuid?: string;
180
+ tracking?: WebhookTracking;
181
+ }
182
+ export interface ConversationCreatedData extends WebhookContactRefs {
183
+ conversation_uuid: string;
184
+ contact_uuid: string;
185
+ channel_uuid: string;
186
+ status: string;
187
+ }
188
+ /** `unread_count` is only present when an inbound message triggered the update. */
189
+ export interface ConversationUpdatedData extends ConversationCreatedData {
190
+ unread_count?: number;
191
+ last_message_at?: string | null;
192
+ }
193
+ export interface FlowExecutionUpdatedData {
194
+ operation_uuid: string;
195
+ operation_type: 'flow.execute';
196
+ status: OperationStatus;
197
+ result: Record<string, unknown> | null;
198
+ error: {
199
+ code: string;
200
+ message: string | null;
201
+ } | null;
202
+ }
203
+ export interface WebhookTestData {
204
+ integration_uuid: string;
205
+ message: string;
206
+ }
207
+ interface WebhookEnvelope<TType extends WebhookEventType, TData> {
208
+ /** Unique event id. Delivery is at-least-once — deduplicate on this value. */
209
+ id: string;
210
+ type: TType;
211
+ api_version: 'v1';
212
+ occurred_at: string;
213
+ data: TData;
214
+ }
215
+ export type MessageReceivedEvent = WebhookEnvelope<'message.received', MessageReceivedData>;
216
+ export type MessageStatusUpdatedEvent = WebhookEnvelope<'message.status.updated', MessageStatusUpdatedData>;
217
+ export type ConversationCreatedEvent = WebhookEnvelope<'conversation.created', ConversationCreatedData>;
218
+ export type ConversationUpdatedEvent = WebhookEnvelope<'conversation.updated', ConversationUpdatedData>;
219
+ export type FlowExecutionUpdatedEvent = WebhookEnvelope<'flow.execution.updated', FlowExecutionUpdatedData>;
220
+ export type WebhookTestEvent = WebhookEnvelope<'webhook.test', WebhookTestData>;
221
+ /** Discriminated on `type` — narrow it and `data` narrows with it. */
222
+ export type WazapiWebhookEvent = MessageReceivedEvent | MessageStatusUpdatedEvent | ConversationCreatedEvent | ConversationUpdatedEvent | FlowExecutionUpdatedEvent | WebhookTestEvent;
223
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wazapi/sdk",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Official Node.js SDK for the Wazapi Public API v1",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -10,8 +10,7 @@
10
10
  "homepage": "https://wazapi.io",
11
11
  "repository": {
12
12
  "type": "git",
13
- "url": "git+https://github.com/wazap-ai/wazap-v2.git",
14
- "directory": "apps/app/sdk"
13
+ "url": "git+https://github.com/wazap-ai/wazapi-node.git"
15
14
  },
16
15
  "main": "./dist/index.js",
17
16
  "module": "./dist/index.js",
@@ -22,7 +21,10 @@
22
21
  "import": "./dist/index.js"
23
22
  }
24
23
  },
25
- "files": ["dist", "README.md"],
24
+ "files": [
25
+ "dist",
26
+ "README.md"
27
+ ],
26
28
  "engines": {
27
29
  "node": ">=18"
28
30
  },
@@ -31,8 +33,17 @@
31
33
  "test": "npm run build && node --test --experimental-strip-types test/*.test.ts",
32
34
  "prepublishOnly": "npm run build"
33
35
  },
34
- "keywords": ["wazapi", "whatsapp", "api", "sdk", "messaging"],
36
+ "keywords": [
37
+ "wazapi",
38
+ "whatsapp",
39
+ "api",
40
+ "sdk",
41
+ "messaging"
42
+ ],
35
43
  "devDependencies": {
36
44
  "typescript": "^5.6.0"
45
+ },
46
+ "bugs": {
47
+ "url": "https://github.com/wazap-ai/wazapi-node/issues"
37
48
  }
38
49
  }