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.
- package/README.md +127 -25
- package/dist/client.d.ts +56 -1
- package/dist/client.js +76 -30
- package/dist/client.js.map +1 -1
- package/dist/http.d.ts +8 -0
- package/dist/http.js +10 -3
- package/dist/http.js.map +1 -1
- package/dist/index.d.ts +16 -1
- package/dist/index.js +35 -2
- package/dist/index.js.map +1 -1
- package/dist/resources/audience/contact-notes.d.ts +51 -0
- package/dist/resources/audience/contact-notes.js +70 -0
- package/dist/resources/audience/contact-notes.js.map +1 -0
- package/dist/resources/audience/contact-tags.d.ts +43 -0
- package/dist/resources/audience/contact-tags.js +60 -0
- package/dist/resources/audience/contact-tags.js.map +1 -0
- package/dist/resources/audience/contacts.d.ts +76 -0
- package/dist/resources/audience/contacts.js +103 -0
- package/dist/resources/audience/contacts.js.map +1 -0
- package/dist/resources/audience/crm-triggers.d.ts +67 -0
- package/dist/resources/audience/crm-triggers.js +86 -0
- package/dist/resources/audience/crm-triggers.js.map +1 -0
- package/dist/resources/audience/custom-fields.d.ts +54 -0
- package/dist/resources/audience/custom-fields.js +71 -0
- package/dist/resources/audience/custom-fields.js.map +1 -0
- package/dist/resources/audience/export-history.d.ts +52 -0
- package/dist/resources/audience/export-history.js +67 -0
- package/dist/resources/audience/export-history.js.map +1 -0
- package/dist/resources/audience/import-history.d.ts +51 -0
- package/dist/resources/audience/import-history.js +66 -0
- package/dist/resources/audience/import-history.js.map +1 -0
- package/dist/resources/audience/segments.d.ts +49 -0
- package/dist/resources/audience/segments.js +66 -0
- package/dist/resources/audience/segments.js.map +1 -0
- package/dist/resources/audience/webhooks.d.ts +68 -0
- package/dist/resources/audience/webhooks.js +83 -0
- package/dist/resources/audience/webhooks.js.map +1 -0
- package/dist/resources/channels/activity.d.ts +25 -0
- package/dist/resources/channels/activity.js +34 -0
- package/dist/resources/channels/activity.js.map +1 -0
- package/dist/resources/channels/billing.d.ts +46 -0
- package/dist/resources/channels/billing.js +59 -0
- package/dist/resources/channels/billing.js.map +1 -0
- package/dist/resources/channels/channels.d.ts +115 -0
- package/dist/resources/channels/channels.js +129 -0
- package/dist/resources/channels/channels.js.map +1 -0
- package/dist/resources/channels/templates.d.ts +90 -0
- package/dist/resources/channels/templates.js +109 -0
- package/dist/resources/channels/templates.js.map +1 -0
- package/dist/resources/channels/wallet.d.ts +25 -0
- package/dist/resources/channels/wallet.js +34 -0
- package/dist/resources/channels/wallet.js.map +1 -0
- package/dist/resources/channels/webhooks.d.ts +60 -0
- package/dist/resources/channels/webhooks.js +79 -0
- package/dist/resources/channels/webhooks.js.map +1 -0
- package/dist/resources/paths.d.ts +12 -0
- package/dist/resources/paths.js +17 -0
- package/dist/resources/paths.js.map +1 -0
- package/dist/types/audience.d.ts +512 -0
- package/dist/types/audience.js +3 -0
- package/dist/types/audience.js.map +1 -0
- package/dist/types/channels.d.ts +383 -0
- package/dist/types/channels.js +22 -0
- package/dist/types/channels.js.map +1 -0
- package/dist/types/common.d.ts +55 -0
- package/dist/types/common.js +8 -0
- package/dist/types/common.js.map +1 -0
- package/dist/types/contact-filter.d.ts +55 -0
- package/dist/types/contact-filter.js +11 -0
- package/dist/types/contact-filter.js.map +1 -0
- package/dist/types/index.d.ts +4 -0
- package/dist/types/index.js +21 -0
- package/dist/types/index.js.map +1 -0
- package/docs/audience/contact-notes.md +129 -0
- package/docs/audience/contact-tags.md +109 -0
- package/docs/audience/contacts.md +227 -0
- package/docs/audience/crm-triggers.md +240 -0
- package/docs/audience/custom-fields.md +174 -0
- package/docs/audience/exports.md +163 -0
- package/docs/audience/imports.md +161 -0
- package/docs/audience/segments.md +120 -0
- package/docs/audience/webhooks.md +167 -0
- package/docs/channels/activity.md +68 -0
- package/docs/channels/billing.md +112 -0
- package/docs/channels/channel-props.md +290 -0
- package/docs/channels/channels.md +199 -0
- package/docs/channels/templates.md +224 -0
- package/docs/channels/wallet.md +67 -0
- package/docs/channels/webhooks.md +152 -0
- package/docs/contact-filter.md +157 -0
- package/package.json +7 -3
- package/dist/resources/channels.d.ts +0 -10
- package/dist/resources/channels.js +0 -19
- package/dist/resources/channels.js.map +0 -1
- package/dist/types.d.ts +0 -70
- package/dist/types.js +0 -7
- 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.
|