ab-ecosystem-cpass 0.1.0 → 1.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 (97) hide show
  1. package/README.md +127 -25
  2. package/dist/client.d.ts +56 -1
  3. package/dist/client.js +76 -30
  4. package/dist/client.js.map +1 -1
  5. package/dist/http.d.ts +8 -0
  6. package/dist/http.js +10 -3
  7. package/dist/http.js.map +1 -1
  8. package/dist/index.d.ts +16 -1
  9. package/dist/index.js +35 -2
  10. package/dist/index.js.map +1 -1
  11. package/dist/resources/audience/contact-notes.d.ts +51 -0
  12. package/dist/resources/audience/contact-notes.js +70 -0
  13. package/dist/resources/audience/contact-notes.js.map +1 -0
  14. package/dist/resources/audience/contact-tags.d.ts +43 -0
  15. package/dist/resources/audience/contact-tags.js +60 -0
  16. package/dist/resources/audience/contact-tags.js.map +1 -0
  17. package/dist/resources/audience/contacts.d.ts +76 -0
  18. package/dist/resources/audience/contacts.js +103 -0
  19. package/dist/resources/audience/contacts.js.map +1 -0
  20. package/dist/resources/audience/crm-triggers.d.ts +67 -0
  21. package/dist/resources/audience/crm-triggers.js +86 -0
  22. package/dist/resources/audience/crm-triggers.js.map +1 -0
  23. package/dist/resources/audience/custom-fields.d.ts +54 -0
  24. package/dist/resources/audience/custom-fields.js +71 -0
  25. package/dist/resources/audience/custom-fields.js.map +1 -0
  26. package/dist/resources/audience/export-history.d.ts +52 -0
  27. package/dist/resources/audience/export-history.js +67 -0
  28. package/dist/resources/audience/export-history.js.map +1 -0
  29. package/dist/resources/audience/import-history.d.ts +51 -0
  30. package/dist/resources/audience/import-history.js +66 -0
  31. package/dist/resources/audience/import-history.js.map +1 -0
  32. package/dist/resources/audience/segments.d.ts +49 -0
  33. package/dist/resources/audience/segments.js +66 -0
  34. package/dist/resources/audience/segments.js.map +1 -0
  35. package/dist/resources/audience/webhooks.d.ts +68 -0
  36. package/dist/resources/audience/webhooks.js +83 -0
  37. package/dist/resources/audience/webhooks.js.map +1 -0
  38. package/dist/resources/channels/activity.d.ts +25 -0
  39. package/dist/resources/channels/activity.js +34 -0
  40. package/dist/resources/channels/activity.js.map +1 -0
  41. package/dist/resources/channels/billing.d.ts +46 -0
  42. package/dist/resources/channels/billing.js +59 -0
  43. package/dist/resources/channels/billing.js.map +1 -0
  44. package/dist/resources/channels/channels.d.ts +115 -0
  45. package/dist/resources/channels/channels.js +129 -0
  46. package/dist/resources/channels/channels.js.map +1 -0
  47. package/dist/resources/channels/templates.d.ts +90 -0
  48. package/dist/resources/channels/templates.js +109 -0
  49. package/dist/resources/channels/templates.js.map +1 -0
  50. package/dist/resources/channels/wallet.d.ts +25 -0
  51. package/dist/resources/channels/wallet.js +34 -0
  52. package/dist/resources/channels/wallet.js.map +1 -0
  53. package/dist/resources/channels/webhooks.d.ts +60 -0
  54. package/dist/resources/channels/webhooks.js +79 -0
  55. package/dist/resources/channels/webhooks.js.map +1 -0
  56. package/dist/resources/paths.d.ts +12 -0
  57. package/dist/resources/paths.js +17 -0
  58. package/dist/resources/paths.js.map +1 -0
  59. package/dist/types/audience.d.ts +512 -0
  60. package/dist/types/audience.js +3 -0
  61. package/dist/types/audience.js.map +1 -0
  62. package/dist/types/channels.d.ts +383 -0
  63. package/dist/types/channels.js +22 -0
  64. package/dist/types/channels.js.map +1 -0
  65. package/dist/types/common.d.ts +55 -0
  66. package/dist/types/common.js +8 -0
  67. package/dist/types/common.js.map +1 -0
  68. package/dist/types/contact-filter.d.ts +55 -0
  69. package/dist/types/contact-filter.js +11 -0
  70. package/dist/types/contact-filter.js.map +1 -0
  71. package/dist/types/index.d.ts +4 -0
  72. package/dist/types/index.js +21 -0
  73. package/dist/types/index.js.map +1 -0
  74. package/docs/audience/contact-notes.md +129 -0
  75. package/docs/audience/contact-tags.md +109 -0
  76. package/docs/audience/contacts.md +227 -0
  77. package/docs/audience/crm-triggers.md +240 -0
  78. package/docs/audience/custom-fields.md +174 -0
  79. package/docs/audience/exports.md +163 -0
  80. package/docs/audience/imports.md +161 -0
  81. package/docs/audience/segments.md +120 -0
  82. package/docs/audience/webhooks.md +167 -0
  83. package/docs/channels/activity.md +68 -0
  84. package/docs/channels/billing.md +112 -0
  85. package/docs/channels/channel-props.md +290 -0
  86. package/docs/channels/channels.md +199 -0
  87. package/docs/channels/templates.md +224 -0
  88. package/docs/channels/wallet.md +67 -0
  89. package/docs/channels/webhooks.md +152 -0
  90. package/docs/contact-filter.md +157 -0
  91. package/package.json +7 -3
  92. package/dist/resources/channels.d.ts +0 -10
  93. package/dist/resources/channels.js +0 -19
  94. package/dist/resources/channels.js.map +0 -1
  95. package/dist/types.d.ts +0 -70
  96. package/dist/types.js +0 -7
  97. package/dist/types.js.map +0 -1
@@ -0,0 +1,199 @@
1
+ # Channels
2
+
3
+ `crm.channels` — `/v1/channels`
4
+
5
+ The connections an account sends and receives on: a WhatsApp number, an RCS
6
+ agent, a webchat widget.
7
+
8
+ A channel is **provisioned by onboarding, not by this API**. There is no
9
+ `create` and no `delete`: `disable` retires one for good, keeping the row its
10
+ billing, wallet and activity history hang off.
11
+
12
+ Anything that lives with the provider rather than on the channel document — the
13
+ business profile, the QR code, media uploads, number registration — moves
14
+ through `getProp` and `setProp`, documented on their own page:
15
+ **[channel properties](channel-props.md)**.
16
+
17
+ | Action | Route | Scope |
18
+ | --- | --- | --- |
19
+ | [`list`](#list) | `GET /v1/channels` | `channels:read` |
20
+ | [`get`](#get) | `GET /v1/channels/:id` | `channels:read` |
21
+ | [`updateDetails`](#updatedetails) | `PUT /v1/channels/:id` | `channels:write` |
22
+ | [`disable`](#disable) | `POST /v1/channels/:id/disable` | `channels:write` |
23
+ | [`rotateToken`](#rotatetoken) | `DELETE /v1/channels/:id/token` | `channels:write` |
24
+ | [`getProp`](channel-props.md) | `GET /v1/channels/:id/:prop` | `channels:read` |
25
+ | [`setProp`](channel-props.md) | `POST /v1/channels/:id/:prop` | `channels:write` |
26
+
27
+ ## The channel object
28
+
29
+ Credentials and the private auth block are stripped from every response; what
30
+ remains is safe to hand a console.
31
+
32
+ ```ts
33
+ interface Channel {
34
+ id: string;
35
+ active: boolean; // enabled by the owner — NOT the connection state
36
+ ecom?: { store_status: boolean; store?: string };
37
+ basic_details: { name: string; color_code: string; channel_identity: string };
38
+ meta: {
39
+ category: string; // whatsapp | sms | email | rcs | webchat
40
+ provider: string; // BSP or integration, e.g. EMBEDDED_PNCL_BILLING
41
+ support_template: boolean;
42
+ show_template: boolean;
43
+ bypass_webhook_verification: boolean;
44
+ };
45
+ wallet: { balance: number | 'NULL'; currency: string };
46
+ campaign_tps: number; // messages/second the campaign runner may push; default 10
47
+ upgraded_webhook_listener: boolean;
48
+ cloud: ChannelCloud; // provider identifiers, flattened
49
+ api_only: boolean;
50
+ crm_only: boolean;
51
+ mm_lite_enabled: boolean;
52
+ access_token: string; // short-lived encrypted handle, minted per response
53
+ [key: string]: unknown;
54
+ }
55
+
56
+ interface ChannelCloud {
57
+ instagram_id: string;
58
+ is_instagram_business_login: boolean;
59
+ waba_id: string;
60
+ business: string; // Meta business id
61
+ phone_number_id: string;
62
+ is_on_biz_app: boolean;
63
+ version: string; // Graph API version
64
+ auto_read_receipt: number;
65
+ upgrade_eligibility: boolean;
66
+ wa_biz_registration: 'pending' | 'completed';
67
+ }
68
+ ```
69
+
70
+ `basic_details.channel_identity` is the channel's address: the phone number, the
71
+ handle, the widget id.
72
+
73
+ **Wallet balances are divided down for display.** They are stored as
74
+ ten-thousandths; `channel.wallet.balance` is the value to four decimal places.
75
+ `'NULL'` means the agency has hidden the wallet from this caller. The
76
+ [wallet ledger](wallet.md) reports the raw stored values instead.
77
+
78
+ ---
79
+
80
+ ## list
81
+
82
+ ```ts
83
+ crm.channels.list(query?: Partial<ListChannelsQuery>): Promise<Paginated<Channel>>
84
+ ```
85
+
86
+ `GET /v1/channels` · `channels:read` · **200**
87
+
88
+ Newest first.
89
+
90
+ | Field | Type | Default | Notes |
91
+ | --- | --- | --- | --- |
92
+ | `page` | number | `1` | 1-based. |
93
+ | `rows` | number | `25` | 1–100. |
94
+ | `search` | string | — | 1–120 chars. Matches the channel **name and identity**. |
95
+ | `type` | `'whatsapp' \| 'sms' \| 'email' \| 'rcs' \| 'webchat'` | — | Quick filter on the category. |
96
+ | `active` | boolean | — | Quick filter: enabled/disabled by the owner. |
97
+ | `status` | `'connected' \| 'disconnected' \| 'error'` | — | Quick filter on connection health. |
98
+
99
+ ```ts
100
+ const { data } = await crm.channels.list({ type: 'whatsapp', active: true });
101
+ ```
102
+
103
+ ---
104
+
105
+ ## get
106
+
107
+ ```ts
108
+ crm.channels.get(id: string): Promise<ChannelResponse>
109
+ ```
110
+
111
+ `GET /v1/channels/:id` · `channels:read` · **200**
112
+
113
+ **Wrapped, and never a 404.** The payload is `{ data }`, and when nothing
114
+ matches it is `{ data: {} }` rather than an error:
115
+
116
+ ```ts
117
+ const { data } = await crm.channels.get(id);
118
+ if (!('id' in data)) throw new Error('no such channel');
119
+ ```
120
+
121
+ ---
122
+
123
+ ## updateDetails
124
+
125
+ ```ts
126
+ crm.channels.updateDetails(id: string, input: UpdateChannelDetailsInput): Promise<ChannelResponse>
127
+ ```
128
+
129
+ `PUT /v1/channels/:id` · `channels:write` · **200**, wrapped as `{ data }`
130
+
131
+ Renames or recolours a channel, and moves its billing currency. **At least one
132
+ field is required** — an empty body is a `422`.
133
+
134
+ | Field | Type | Notes |
135
+ | --- | --- | --- |
136
+ | `name` | string | 1–120 chars. |
137
+ | `color` | string | Hex, `#075E54` or `#fff`. |
138
+ | `currency` | `'USD' \| 'EUR' \| 'INR' \| 'IDR'` | See below. |
139
+
140
+ Currency is the one field with consequences: it decides how every conversation
141
+ is priced, so it only moves on a **WhatsApp channel with an empty wallet** — a
142
+ balance carried across a switch would be worth a different amount on the other
143
+ side. A non-empty wallet is `409 wallet_not_empty`, with the current currency in
144
+ `details`. Asking for the currency the channel already bills in changes nothing
145
+ and is not an error.
146
+
147
+ ```ts
148
+ await crm.channels.updateDetails(id, { name: 'Support', color: '#075E54' });
149
+ ```
150
+
151
+ ---
152
+
153
+ ## disable
154
+
155
+ ```ts
156
+ crm.channels.disable(id: string): Promise<{ success: true }>
157
+ ```
158
+
159
+ `POST /v1/channels/:id/disable` · `channels:write` · **200**
160
+
161
+ Retires a channel for good. It stops delivering, drops out of every listing, and
162
+ **cannot be brought back**. The row stays so its billing, wallet and activity
163
+ history remain readable.
164
+
165
+ A negative wallet balance blocks it with `409 negative_wallet_balance`: that is
166
+ money owed that nothing would collect afterwards. Settle the balance first.
167
+
168
+ A webchat channel has its widget subscription removed first — left behind, the
169
+ panel would keep posting into a dead inbox, and nothing would be able to reach
170
+ the widget to remove it afterwards.
171
+
172
+ ---
173
+
174
+ ## rotateToken
175
+
176
+ ```ts
177
+ crm.channels.rotateToken(id: string): Promise<{ success: true }>
178
+ ```
179
+
180
+ `DELETE /v1/channels/:id/token` · `channels:write` · **200**
181
+
182
+ Issues a new inbound API token and discards the old one, which **stops working
183
+ immediately**. Anything using it starts failing on the next request.
184
+
185
+ The new value is not in the response. Read it back with:
186
+
187
+ ```ts
188
+ await crm.channels.rotateToken(id);
189
+ const { data: token } = await crm.channels.getProp<string>(id, 'token');
190
+ ```
191
+
192
+ ---
193
+
194
+ ## Properties
195
+
196
+ `getProp` and `setProp` cover everything else a channel can do — the business
197
+ profile, QR codes, media uploads, number registration and migration, the message
198
+ queue, conversation automation. Each property, with its query and body, is on
199
+ **[channel properties](channel-props.md)**.
@@ -0,0 +1,224 @@
1
+ # Templates
2
+
3
+ `crm.templates` — `/v1/channels/:channelId/templates`
4
+
5
+ WhatsApp message templates, per channel.
6
+
7
+ A template is **local until it is submitted to Meta**. Create it as a `draft`,
8
+ or pass `applyForApproval: true` to send it straight on, at which point it
9
+ becomes `pending` and Meta decides. `sync` pulls Meta's current view back over
10
+ ours, which is how approvals and rejections arrive without waiting on a webhook.
11
+
12
+ Every method takes the channel id first. A template id belonging to another
13
+ channel is a `404` rather than a match.
14
+
15
+ | Action | Route | Scope |
16
+ | --- | --- | --- |
17
+ | [`list`](#list) | `GET /v1/channels/:channelId/templates` | `channels:read` |
18
+ | [`get`](#get) | `GET /v1/channels/:channelId/templates/:id` | `channels:read` |
19
+ | [`create`](#create) | `POST /v1/channels/:channelId/templates` | `channels:write` |
20
+ | [`update`](#update) | `PATCH /v1/channels/:channelId/templates/:id` | `channels:write` |
21
+ | [`delete`](#delete) | `DELETE /v1/channels/:channelId/templates/:id` | `channels:write` |
22
+ | [`sync`](#sync) | `POST /v1/channels/:channelId/templates/sync` | `channels:write` |
23
+
24
+ ## The template object
25
+
26
+ ```ts
27
+ interface Template {
28
+ id: string;
29
+ channelId: string;
30
+ type: 'whatsapp';
31
+ status: 'draft' | 'pending' | 'approved' | 'rejected';
32
+ source: 'MANUAL' | 'AUTO_GENERATED';
33
+ templateId?: string; // Meta's id; absent while still a local draft
34
+ name: string;
35
+ category: 'MARKETING' | 'UTILITY' | 'AUTHENTICATION';
36
+ subCategory: string;
37
+ parameterFormat: 'POSITIONAL' | 'NAMED';
38
+ language: string; // e.g. 'en_US'
39
+ hash: string; // identity within the channel: name + language
40
+ adDetails: { ad_account: string; ad_campaign: string; ad_set: string; ad_id: string };
41
+ components: Record<string, unknown>[]; // Meta's HEADER/BODY/FOOTER/BUTTONS array
42
+ payload?: Record<string, unknown>; // the last payload sent to Meta
43
+ extra?: Record<string, unknown>;
44
+ createdAt: string;
45
+ updatedAt: string;
46
+ }
47
+ ```
48
+
49
+ **Identity is name plus language.** The same name in two languages is two
50
+ templates; that pair is what `hash` encodes.
51
+
52
+ ### Lifecycle
53
+
54
+ ```
55
+ draft ──applyForApproval──▶ pending ──▶ approved
56
+ ▲ │
57
+ └── stays local └──────▶ rejected
58
+ ```
59
+
60
+ `AUTO_GENERATED` templates are created by the platform, not by a caller.
61
+
62
+ ---
63
+
64
+ ## list
65
+
66
+ ```ts
67
+ crm.templates.list(channelId: string, query?: Partial<ListTemplatesQuery>): Promise<Paginated<Template>>
68
+ ```
69
+
70
+ `GET /v1/channels/:channelId/templates` · `channels:read` · **200**
71
+
72
+ | Field | Type | Default | Notes |
73
+ | --- | --- | --- | --- |
74
+ | `page` | number | `1` | 1-based. |
75
+ | `rows` | number | `25` | 1–100. |
76
+ | `search` | string | — | 1–120 chars. Matches the **name and sub-category**. |
77
+ | `status` | `TemplateStatus` | — | Quick filter. |
78
+ | `category` | `TemplateCategory` | — | Quick filter. |
79
+ | `parameterFormat` | `'POSITIONAL' \| 'NAMED'` | — | Quick filter. |
80
+ | `language` | string | — | Exact language code. |
81
+
82
+ ```ts
83
+ const approved = await crm.templates.list(channelId, { status: 'approved', language: 'en_US' });
84
+ ```
85
+
86
+ ---
87
+
88
+ ## get
89
+
90
+ ```ts
91
+ crm.templates.get(channelId: string, id: string): Promise<Template>
92
+ ```
93
+
94
+ `GET /v1/channels/:channelId/templates/:id` · `channels:read` · **200**
95
+
96
+ ---
97
+
98
+ ## create
99
+
100
+ ```ts
101
+ crm.templates.create(channelId: string, input: CreateTemplateInput): Promise<Template>
102
+ ```
103
+
104
+ `POST /v1/channels/:channelId/templates` · `channels:write` · **201**
105
+
106
+ ### Body
107
+
108
+ | Field | Type | Required | Default | Notes |
109
+ | --- | --- | --- | --- | --- |
110
+ | `name` | string | yes | — | 1–512 chars, `^[a-z0-9_]+$` — Meta's own rule. |
111
+ | `category` | `'MARKETING' \| 'UTILITY' \| 'AUTHENTICATION'` | yes | — | |
112
+ | `language` | string | yes | — | 2–15 chars, e.g. `en` or `en_US`. |
113
+ | `components` | object[] | yes | — | At least one. Meta's component array. |
114
+ | `subCategory` | string | no | `''` | Up to 120 chars. |
115
+ | `parameterFormat` | `'POSITIONAL' \| 'NAMED'` | no | `'POSITIONAL'` | `{{1}}` vs `{{name}}`. |
116
+ | `source` | `'MANUAL' \| 'AUTO_GENERATED'` | no | `'MANUAL'` | |
117
+ | `payload` | object | no | — | |
118
+ | `extra` | object | no | — | |
119
+ | `adDetails` | partial | no | all `''` | Click-to-WhatsApp ad attribution. |
120
+ | `applyForApproval` | boolean | no | `false` | Submit to Meta immediately. |
121
+
122
+ Re-using a `name` + `language` pair that already exists on the channel is a
123
+ `409 conflict`.
124
+
125
+ **Meta goes first.** With `applyForApproval: true`, the template is submitted
126
+ before any row is written, so a rejection leaves nothing behind — it surfaces as
127
+ a `400` carrying Meta's reason.
128
+
129
+ ```ts
130
+ const template = await crm.templates.create(channelId, {
131
+ name: 'order_update',
132
+ category: 'UTILITY',
133
+ language: 'en_US',
134
+ components: [
135
+ { type: 'BODY', text: 'Hi {{1}}, your order {{2}} has shipped.' },
136
+ ],
137
+ applyForApproval: true,
138
+ });
139
+
140
+ template.status; // 'pending'
141
+ ```
142
+
143
+ ---
144
+
145
+ ## update
146
+
147
+ ```ts
148
+ crm.templates.update(channelId: string, id: string, input: UpdateTemplateInput): Promise<Template>
149
+ ```
150
+
151
+ `PATCH /v1/channels/:channelId/templates/:id` · `channels:write` · **200**
152
+
153
+ Every field from `create`, plus `status`, all optional. At least one is required.
154
+
155
+ ### What may change depends on where the template is
156
+
157
+ A **draft** is fully editable. **Once it is with Meta its identity is fixed**,
158
+ and `name`, `language`, `category`, `subCategory`, `parameterFormat`, `source`
159
+ and `adDetails` are silently ignored — only `components`, `payload` and `extra`
160
+ still move.
161
+
162
+ ### Nothing is sent to Meta unless you ask
163
+
164
+ Pass `applyForApproval: true`, or a `status` other than `'draft'`. Then:
165
+
166
+ - a **draft** is submitted for the first time;
167
+ - a template **already at Meta** is updated in place.
168
+
169
+ Either way the row comes back `pending`. A non-draft with no Meta id is
170
+ `409 template_not_submitted`.
171
+
172
+ Changing a draft's `name` or `language` into a pair that already exists is a
173
+ `409 conflict`.
174
+
175
+ ```ts
176
+ // Edit the copy and resubmit
177
+ await crm.templates.update(channelId, id, {
178
+ components: [{ type: 'BODY', text: 'Hi {{1}}, order {{2}} is on its way.' }],
179
+ applyForApproval: true,
180
+ });
181
+ ```
182
+
183
+ ---
184
+
185
+ ## delete
186
+
187
+ ```ts
188
+ crm.templates.delete(channelId: string, id: string): Promise<void>
189
+ ```
190
+
191
+ `DELETE /v1/channels/:channelId/templates/:id` · `channels:write` · **204**, no body.
192
+
193
+ A **draft** only exists here and is simply removed. Anything submitted is
194
+ **deleted at Meta first**, so no row is kept for a template the WABA no longer
195
+ has. If Meta refuses, the call fails with `400 template_deletion_failed` and the
196
+ row stays.
197
+
198
+ ---
199
+
200
+ ## sync
201
+
202
+ ```ts
203
+ crm.templates.sync(channelId: string): Promise<TemplateSyncResult>
204
+ ```
205
+
206
+ `POST /v1/channels/:channelId/templates/sync` · `channels:write` · **200**
207
+
208
+ Pulls the channel's templates from Meta and writes their current state over
209
+ ours, so approvals and rejections show up without waiting for a webhook.
210
+
211
+ Note the path: **`sync` is a sibling of `:id`, not a child of it.**
212
+
213
+ ```ts
214
+ { fetched: number, created: number, updated: number, skipped: number }
215
+ ```
216
+
217
+ `skipped` is templates Meta returned that are not storable here — one in a
218
+ category this platform does not model, for instance. `400 template_sync_failed`
219
+ if Meta refuses the listing.
220
+
221
+ ```ts
222
+ const result = await crm.templates.sync(channelId);
223
+ console.log(`${result.updated} templates changed status`);
224
+ ```
@@ -0,0 +1,67 @@
1
+ # Wallet ledger
2
+
3
+ `crm.wallet` — `/v1/channels/:channelId/wallet`
4
+
5
+ Every credit and debit against a channel's balance, with the balance before and
6
+ after each movement.
7
+
8
+ **Read-only.** Money moves through top-ups and through conversation billing, not
9
+ through this API. To send a user to the top-up page, read
10
+ [`recharge_url`](channel-props.md#recharge_url).
11
+
12
+ | Action | Route | Scope |
13
+ | --- | --- | --- |
14
+ | [`list`](#list) | `GET /v1/channels/:channelId/wallet` | `channels:read` |
15
+
16
+ ## The wallet log object
17
+
18
+ ```ts
19
+ interface WalletLog {
20
+ id: string;
21
+ type: 'credit' | 'debit';
22
+ currency: string;
23
+ note: string;
24
+ amount: number; // ten-thousandths
25
+ startBalance: number; // ten-thousandths
26
+ endBalance: number; // ten-thousandths
27
+ status: string;
28
+ category: string;
29
+ createdAt: string;
30
+ updatedAt: string;
31
+ }
32
+ ```
33
+
34
+ **Amounts here are the raw stored values** — ten-thousandths of a currency unit
35
+ — unlike `channel.wallet.balance`, which is already divided down for display.
36
+ Divide by `10000` to show them:
37
+
38
+ ```ts
39
+ const display = (amount: number) => (amount / 10_000).toFixed(4);
40
+ ```
41
+
42
+ ---
43
+
44
+ ## list
45
+
46
+ ```ts
47
+ crm.wallet.list(channelId: string, query?: Partial<ListWalletLogsQuery>): Promise<Paginated<WalletLog>>
48
+ ```
49
+
50
+ `GET /v1/channels/:channelId/wallet` · `channels:read` · **200**
51
+
52
+ Newest first.
53
+
54
+ | Field | Type | Default | Notes |
55
+ | --- | --- | --- | --- |
56
+ | `page` | number | `1` | 1-based. |
57
+ | `rows` | number | `25` | 1–100. |
58
+ | `search` | string | — | 1–120 chars. Matches the **note, category and status**. |
59
+ | `type` | `'credit' \| 'debit'` | — | Quick filter. |
60
+ | `range` | `{ start, end }` | — | Unix seconds. `0` on either end means no bound. Serialised to JSON by the SDK. |
61
+
62
+ ```ts
63
+ const topUps = await crm.wallet.list(channelId, {
64
+ type: 'credit',
65
+ range: { start: 1767225600, end: 0 },
66
+ });
67
+ ```
@@ -0,0 +1,152 @@
1
+ # Channel webhooks
2
+
3
+ `crm.channelWebhooks` — `/v1/channels/:channelId/webhooks`
4
+
5
+ Where a channel's message traffic is delivered.
6
+
7
+ These carry conversation events — `incoming`, `sent`, `delivered`, `read`,
8
+ `failed` — plus the account-level notices Meta sends about templates, the phone
9
+ number and the WABA. Several webhooks may watch the same channel, and each gets
10
+ its own copy.
11
+
12
+ Distinct from [`crm.webhooks`](../audience/webhooks.md), which carries contact
13
+ changes from the audience module.
14
+
15
+ | Action | Route | Scope |
16
+ | --- | --- | --- |
17
+ | [`list`](#list) | `GET /v1/channels/:channelId/webhooks` | `channels:read` |
18
+ | [`get`](#get) | `GET /v1/channels/:channelId/webhooks/:id` | `channels:read` |
19
+ | [`create`](#create) | `POST /v1/channels/:channelId/webhooks` | `channels:write` |
20
+ | [`update`](#update) | `PATCH /v1/channels/:channelId/webhooks/:id` | `channels:write` |
21
+ | [`delete`](#delete) | `DELETE /v1/channels/:channelId/webhooks/:id` | `channels:write` |
22
+
23
+ ## The webhook object
24
+
25
+ ```ts
26
+ interface ChannelWebhook {
27
+ id: string;
28
+ channelId: string;
29
+ title: string;
30
+ active: boolean;
31
+ events: ChannelWebhookEvent[];
32
+ url: string;
33
+ meta?: {
34
+ source: 'PANEL' | 'API';
35
+ createdBy?: string;
36
+ createdIp?: string;
37
+ updatedBy?: string;
38
+ updatedIp?: string;
39
+ };
40
+ createdAt: string;
41
+ updatedAt: string;
42
+ }
43
+ ```
44
+
45
+ `meta` is an **audit trail**: who created or last changed the webhook and from
46
+ where. It is filled in by the service from the caller's identity and IP, and is
47
+ never accepted on a write. Webhooks created through this SDK are recorded with
48
+ `source: 'API'`.
49
+
50
+ ### Events
51
+
52
+ | Event | Fires on |
53
+ | --- | --- |
54
+ | `incoming` | A message from the customer. |
55
+ | `sent` | A message accepted for delivery. |
56
+ | `delivered` | Delivery confirmed. |
57
+ | `read` | The customer read it. |
58
+ | `failed` | Delivery failed. |
59
+ | `deleted` | A message was deleted. |
60
+ | `template` | A template's status changed at Meta. |
61
+ | `account` | A WABA-level notice. |
62
+ | `phone_number` | A phone-number-level notice. |
63
+ | `calls` | A call event. |
64
+
65
+ Exported as `CHANNEL_WEBHOOK_EVENTS` if you need the list at runtime.
66
+
67
+ ---
68
+
69
+ ## list
70
+
71
+ ```ts
72
+ crm.channelWebhooks.list(channelId: string, query?: Partial<ListChannelWebhooksQuery>): Promise<Paginated<ChannelWebhook>>
73
+ ```
74
+
75
+ `GET /v1/channels/:channelId/webhooks` · `channels:read` · **200**
76
+
77
+ | Field | Type | Default | Notes |
78
+ | --- | --- | --- | --- |
79
+ | `page` | number | `1` | 1-based. |
80
+ | `rows` | number | `25` | 1–100. |
81
+ | `search` | string | — | 1–120 chars. Matches the **title and URL**. |
82
+ | `active` | boolean | — | Quick filter: enabled/disabled. |
83
+ | `event` | `ChannelWebhookEvent` | — | Quick filter: webhooks subscribed to this one event. |
84
+
85
+ ```ts
86
+ const live = await crm.channelWebhooks.list(channelId, { active: true, event: 'incoming' });
87
+ ```
88
+
89
+ ---
90
+
91
+ ## get
92
+
93
+ ```ts
94
+ crm.channelWebhooks.get(channelId: string, id: string): Promise<ChannelWebhook>
95
+ ```
96
+
97
+ `GET /v1/channels/:channelId/webhooks/:id` · `channels:read` · **200**
98
+
99
+ ---
100
+
101
+ ## create
102
+
103
+ ```ts
104
+ crm.channelWebhooks.create(channelId: string, input: CreateChannelWebhookInput): Promise<ChannelWebhook>
105
+ ```
106
+
107
+ `POST /v1/channels/:channelId/webhooks` · `channels:write` · **201**
108
+
109
+ ### Body
110
+
111
+ | Field | Type | Required | Default | Notes |
112
+ | --- | --- | --- | --- | --- |
113
+ | `url` | string | yes | — | Valid absolute URL, up to 2048 chars. |
114
+ | `events` | `ChannelWebhookEvent[]` | yes | — | At least one. |
115
+ | `title` | string | no | `'CRM Channel Webhook'` | 1–120 chars. |
116
+ | `active` | boolean | no | `true` | |
117
+
118
+ ```ts
119
+ await crm.channelWebhooks.create(channelId, {
120
+ title: 'Inbox bridge',
121
+ url: 'https://inbox.example.com/hooks/whatsapp',
122
+ events: ['incoming', 'sent', 'delivered', 'read', 'failed'],
123
+ });
124
+ ```
125
+
126
+ ---
127
+
128
+ ## update
129
+
130
+ ```ts
131
+ crm.channelWebhooks.update(channelId: string, id: string, input: UpdateChannelWebhookInput): Promise<ChannelWebhook>
132
+ ```
133
+
134
+ `PATCH /v1/channels/:channelId/webhooks/:id` · `channels:write` · **200**
135
+
136
+ Same fields as `create`, all optional; at least one is required.
137
+
138
+ `events` **replaces** the subscription list rather than adding to it. Pause
139
+ delivery with `{ active: false }` rather than deleting, so the configuration
140
+ comes back.
141
+
142
+ ---
143
+
144
+ ## delete
145
+
146
+ ```ts
147
+ crm.channelWebhooks.delete(channelId: string, id: string): Promise<void>
148
+ ```
149
+
150
+ `DELETE /v1/channels/:channelId/webhooks/:id` · `channels:write` · **204**
151
+
152
+ Delivery stops immediately.