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,167 @@
|
|
|
1
|
+
# Audience webhooks
|
|
2
|
+
|
|
3
|
+
`crm.webhooks` — `/v1/audience/webhooks`
|
|
4
|
+
|
|
5
|
+
HTTPS endpoints the platform POSTs contact events to.
|
|
6
|
+
|
|
7
|
+
Three things in this SDK are called a webhook; they are not the same thing:
|
|
8
|
+
|
|
9
|
+
| | Carries | Signed |
|
|
10
|
+
| --- | --- | --- |
|
|
11
|
+
| `crm.webhooks` | Contact events: created, updated, deleted | **yes** |
|
|
12
|
+
| [`crm.crmTriggers`](crm-triggers.md) | One configured request per rule, when a contact matches | no |
|
|
13
|
+
| [`crm.channelWebhooks`](../channels/webhooks.md) | A channel's message traffic | n/a |
|
|
14
|
+
|
|
15
|
+
**Scoping** — unlike the rest of the audience module, these rows are scoped by
|
|
16
|
+
**tenant** rather than by the calling user.
|
|
17
|
+
|
|
18
|
+
| Action | Route | Scope |
|
|
19
|
+
| --- | --- | --- |
|
|
20
|
+
| [`list`](#list) | `GET /v1/audience/webhooks` | `webhooks:read` |
|
|
21
|
+
| [`get`](#get) | `GET /v1/audience/webhooks/:id` | `webhooks:read` |
|
|
22
|
+
| [`create`](#create) | `POST /v1/audience/webhooks` | `webhooks:write` |
|
|
23
|
+
| [`update`](#update) | `PATCH /v1/audience/webhooks/:id` | `webhooks:write` |
|
|
24
|
+
| [`delete`](#delete) | `DELETE /v1/audience/webhooks/:id` | `webhooks:write` |
|
|
25
|
+
|
|
26
|
+
## The webhook object
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
interface Webhook {
|
|
30
|
+
id: string;
|
|
31
|
+
url: string;
|
|
32
|
+
events: ('contact.created' | 'contact.updated' | 'contact.deleted')[];
|
|
33
|
+
enabled: boolean;
|
|
34
|
+
failureCount: number; // consecutive delivery failures
|
|
35
|
+
lastDeliveredAt?: string;
|
|
36
|
+
createdAt: string;
|
|
37
|
+
updatedAt: string;
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## The delivery
|
|
42
|
+
|
|
43
|
+
Your endpoint receives:
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
interface WebhookEnvelope {
|
|
47
|
+
id: string; // delivery id, stable across retries of the same event
|
|
48
|
+
type: string; // e.g. 'contact.created'
|
|
49
|
+
tenantId: string;
|
|
50
|
+
createdAt: string;
|
|
51
|
+
data: unknown; // the contact, or { id } for a deletion
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
with two headers:
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
x-crm-timestamp: 1774512000
|
|
59
|
+
x-crm-signature: v1=<hex>
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
where the digest is `HMAC-SHA256(secret, "<timestamp>.<raw body>")`. Verify
|
|
63
|
+
against the **raw bytes**, before any JSON parsing:
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
import { createHmac, timingSafeEqual } from 'node:crypto';
|
|
67
|
+
|
|
68
|
+
const verify = (secret: string, timestamp: string, rawBody: string, header: string) => {
|
|
69
|
+
const digest = createHmac('sha256', secret).update(`${timestamp}.${rawBody}`).digest('hex');
|
|
70
|
+
const expected = Buffer.from(`v1=${digest}`);
|
|
71
|
+
const actual = Buffer.from(header);
|
|
72
|
+
return expected.length === actual.length && timingSafeEqual(expected, actual);
|
|
73
|
+
};
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Reject a timestamp too far from now to stop replays.
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## list
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
crm.webhooks.list(query?: Partial<ListWebhooksQuery>): Promise<Page<Webhook>>
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
`GET /v1/audience/webhooks` · `webhooks:read` · **200**
|
|
87
|
+
|
|
88
|
+
**Cursor paginated**, not offset.
|
|
89
|
+
|
|
90
|
+
| Field | Type | Default | Notes |
|
|
91
|
+
| --- | --- | --- | --- |
|
|
92
|
+
| `limit` | number | `50` | 1–100. |
|
|
93
|
+
| `cursor` | string | — | From the previous response's `nextCursor`. |
|
|
94
|
+
| `enabled` | boolean | — | Accepted by the contract, but **not yet applied** by the service — the list comes back unfiltered. Filter client-side until that lands. |
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## get
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
crm.webhooks.get(id: string): Promise<Webhook>
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
`GET /v1/audience/webhooks/:id` · `webhooks:read` · **200**
|
|
105
|
+
|
|
106
|
+
The signing secret is **never** returned here. It is shown once, by `create`.
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## create
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
crm.webhooks.create(input: CreateWebhookInput): Promise<Webhook & { secret: string }>
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
`POST /v1/audience/webhooks` · `webhooks:write` · **201**
|
|
117
|
+
|
|
118
|
+
### Body
|
|
119
|
+
|
|
120
|
+
| Field | Type | Required | Default | Notes |
|
|
121
|
+
| --- | --- | --- | --- | --- |
|
|
122
|
+
| `url` | string | yes | — | Must be a valid absolute URL. |
|
|
123
|
+
| `events` | `WebhookEventType[]` | yes | — | At least one of `contact.created`, `contact.updated`, `contact.deleted`. |
|
|
124
|
+
| `enabled` | boolean | no | `true` | |
|
|
125
|
+
|
|
126
|
+
The URL has to be **public https**. A non-https URL, or a hostname resolving to
|
|
127
|
+
a loopback, link-local or private address, is rejected with
|
|
128
|
+
`422 invalid_webhook_url` — the DNS lookup happens at write time, so a name that
|
|
129
|
+
resolves privately is caught here rather than at delivery.
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
const hook = await crm.webhooks.create({
|
|
133
|
+
url: 'https://ops.example.com/hooks/crm',
|
|
134
|
+
events: ['contact.created', 'contact.updated'],
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
await vault.store(hook.secret); // the only response that carries it
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## update
|
|
143
|
+
|
|
144
|
+
```ts
|
|
145
|
+
crm.webhooks.update(id: string, input: UpdateWebhookInput): Promise<Webhook>
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
`PATCH /v1/audience/webhooks/:id` · `webhooks:write` · **200**
|
|
149
|
+
|
|
150
|
+
Same fields as `create`, all optional. A new `url` is checked the same way.
|
|
151
|
+
`events` **replaces** the subscription list rather than adding to it.
|
|
152
|
+
|
|
153
|
+
The secret does not change and cannot be rotated through this route — create a
|
|
154
|
+
second webhook, move over, and delete the first.
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## delete
|
|
159
|
+
|
|
160
|
+
```ts
|
|
161
|
+
crm.webhooks.delete(id: string): Promise<void>
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
`DELETE /v1/audience/webhooks/:id` · `webhooks:write` · **204**, no body.
|
|
165
|
+
|
|
166
|
+
Delivery stops immediately; queued attempts are dropped. To pause instead, send
|
|
167
|
+
`update(id, { enabled: false })`.
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Activity log
|
|
2
|
+
|
|
3
|
+
`crm.activity` — `/v1/channels/:channelId/logs`
|
|
4
|
+
|
|
5
|
+
What happened to a channel, and who did it — the owner, the agency, or the
|
|
6
|
+
platform itself.
|
|
7
|
+
|
|
8
|
+
**Read-only**, and written by the services as they work. Wallet movements share
|
|
9
|
+
the feed with ordinary activity, so `type: 'activity'` is the filter for
|
|
10
|
+
"everything except money"; for the money itself with its running balance, read
|
|
11
|
+
the [wallet ledger](wallet.md) instead.
|
|
12
|
+
|
|
13
|
+
| Action | Route | Scope |
|
|
14
|
+
| --- | --- | --- |
|
|
15
|
+
| [`list`](#list) | `GET /v1/channels/:channelId/logs` | `channels:read` |
|
|
16
|
+
|
|
17
|
+
## The activity log object
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
interface ActivityLog {
|
|
21
|
+
id: string;
|
|
22
|
+
source: {
|
|
23
|
+
type?: 'user' | 'agency' | 'system';
|
|
24
|
+
id?: string;
|
|
25
|
+
};
|
|
26
|
+
log: {
|
|
27
|
+
type?: 'wallet' | 'activity';
|
|
28
|
+
event: string; // machine name, e.g. 'template.approved'
|
|
29
|
+
message: string; // human-readable line for the feed
|
|
30
|
+
};
|
|
31
|
+
createdAt: string;
|
|
32
|
+
updatedAt: string;
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## list
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
crm.activity.list(channelId: string, query?: Partial<ListActivityLogsQuery>): Promise<Paginated<ActivityLog>>
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
`GET /v1/channels/:channelId/logs` · `channels:read` · **200**
|
|
45
|
+
|
|
46
|
+
Newest first.
|
|
47
|
+
|
|
48
|
+
| Field | Type | Default | Notes |
|
|
49
|
+
| --- | --- | --- | --- |
|
|
50
|
+
| `page` | number | `1` | 1-based. |
|
|
51
|
+
| `rows` | number | `25` | 1–100. |
|
|
52
|
+
| `search` | string | — | 1–120 chars. Matches the **event name and the message**. |
|
|
53
|
+
| `type` | `'wallet' \| 'activity'` | — | Quick filter. |
|
|
54
|
+
| `source` | `'user' \| 'agency' \| 'system'` | — | Quick filter: who caused the entry. |
|
|
55
|
+
| `event` | string | — | Quick filter: one exact event name, 1–120 chars. |
|
|
56
|
+
| `range` | `{ start, end }` | — | Unix seconds. `0` on either end means no bound. Serialised to JSON by the SDK. |
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
// Everything the platform did to this channel this week
|
|
60
|
+
const feed = await crm.activity.list(channelId, {
|
|
61
|
+
source: 'system',
|
|
62
|
+
type: 'activity',
|
|
63
|
+
range: { start: weekStart, end: 0 },
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// One event's history
|
|
67
|
+
const approvals = await crm.activity.list(channelId, { event: 'template.approved' });
|
|
68
|
+
```
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# Conversation billing
|
|
2
|
+
|
|
3
|
+
`crm.billing` — `/v1/channels/:channelId/conversations`
|
|
4
|
+
|
|
5
|
+
What each conversation on a channel cost.
|
|
6
|
+
|
|
7
|
+
One route answers in two shapes. Without `stat` it pages through individual
|
|
8
|
+
charges; with `stat` it returns aggregates instead — which is why this SDK
|
|
9
|
+
splits it into two methods.
|
|
10
|
+
|
|
11
|
+
**`channelId` may be `'all'`** to read across every channel the caller owns.
|
|
12
|
+
That also changes what `summary` groups by: currency across all channels,
|
|
13
|
+
message category on a single one.
|
|
14
|
+
|
|
15
|
+
| Action | Route | Scope |
|
|
16
|
+
| --- | --- | --- |
|
|
17
|
+
| [`list`](#list) | `GET /v1/channels/:channelId/conversations` | `channels:read` |
|
|
18
|
+
| [`stats`](#stats) | `GET /v1/channels/:channelId/conversations?stat=…` | `channels:read` |
|
|
19
|
+
|
|
20
|
+
Read-only: charges are written by the platform as conversations happen.
|
|
21
|
+
|
|
22
|
+
## The billing log object
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
interface BillingLog {
|
|
26
|
+
id: string;
|
|
27
|
+
chatId?: string; // the conversation the charge belongs to
|
|
28
|
+
identifier: string; // the recipient, as the channel addresses them
|
|
29
|
+
deduction: {
|
|
30
|
+
billable: boolean; // false for free-tier conversations — logged, not charged
|
|
31
|
+
amount: number;
|
|
32
|
+
currency: string;
|
|
33
|
+
};
|
|
34
|
+
meta?: Record<string, unknown>; // recipient country, category, template, campaign, message id
|
|
35
|
+
createdAt: string;
|
|
36
|
+
updatedAt: string;
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## list
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
crm.billing.list(channelId: string, query?): Promise<Paginated<BillingLog>>
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
`GET /v1/channels/:channelId/conversations` · `channels:read` · **200**
|
|
49
|
+
|
|
50
|
+
| Field | Type | Default | Notes |
|
|
51
|
+
| --- | --- | --- | --- |
|
|
52
|
+
| `page` | number | `1` | 1-based. |
|
|
53
|
+
| `rows` | number | `25` | 1–100. |
|
|
54
|
+
| `search` | string | — | 1–120 chars. Matches the recipient `identifier` and the `meta` fields: recipient, category, country and message id. |
|
|
55
|
+
| `range` | `{ start, end }` | — | Unix seconds. `0` on either end means **no bound**. Serialised to JSON by the SDK. |
|
|
56
|
+
| `template` | string[] | — | Quick filter: templates the conversation was billed against. |
|
|
57
|
+
| `country` | string[] | — | Quick filter: recipient country codes. |
|
|
58
|
+
| `campaign` | string[] | — | Quick filter: campaigns that produced the conversation. |
|
|
59
|
+
|
|
60
|
+
The three array filters are sent as JSON; the server also accepts a
|
|
61
|
+
comma-separated string if you build the query yourself.
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
const charges = await crm.billing.list('all', {
|
|
65
|
+
range: { start: 1767225600, end: 1769817600 },
|
|
66
|
+
country: ['IN', 'US'],
|
|
67
|
+
rows: 100,
|
|
68
|
+
});
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## stats
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
crm.billing.stats(channelId: string, stat?: BillingStat, query?): Promise<BillingStats>
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
`GET /v1/channels/:channelId/conversations?stat=…` · `channels:read` · **200**
|
|
80
|
+
|
|
81
|
+
The same filters, aggregated instead of paged. `stat` defaults to `'summary'`.
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
interface BillingStats {
|
|
85
|
+
data: { group: string; totalRecords: number; totalDeductionAmount: number }[];
|
|
86
|
+
stat: 'summary' | 'day_wise';
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
`group` is what the totals are cut by:
|
|
91
|
+
|
|
92
|
+
| `stat` | `channelId` | `group` is |
|
|
93
|
+
| --- | --- | --- |
|
|
94
|
+
| `summary` | `'all'` | the currency |
|
|
95
|
+
| `summary` | one channel | the message category |
|
|
96
|
+
| `day_wise` | either | the calendar date |
|
|
97
|
+
|
|
98
|
+
**Pagination does not apply** — every group is returned, and `page`/`rows` are
|
|
99
|
+
ignored.
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
const monthly = await crm.billing.stats('all', 'summary', {
|
|
103
|
+
range: { start: 1767225600, end: 1769817600 },
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
for (const row of monthly.data) {
|
|
107
|
+
console.log(row.group, row.totalDeductionAmount, `(${row.totalRecords} conversations)`);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Per day, one channel
|
|
111
|
+
const daily = await crm.billing.stats(channelId, 'day_wise', { range: { start: 0, end: 0 } });
|
|
112
|
+
```
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
# Channel properties
|
|
2
|
+
|
|
3
|
+
`crm.channels.getProp` / `crm.channels.setProp` — `/v1/channels/:id/:prop`
|
|
4
|
+
|
|
5
|
+
Everything about a channel that lives with the **provider** rather than on the
|
|
6
|
+
channel document: the business profile, QR codes, media uploads, number
|
|
7
|
+
registration, the message queue.
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
crm.channels.getProp<T>(id, prop, query?): Promise<ChannelPropResponse<T>>
|
|
11
|
+
crm.channels.setProp<T>(id, prop, options?): Promise<ChannelPropResponse<T>>
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Both answer the same envelope:
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
interface ChannelPropResponse<T> {
|
|
18
|
+
data: T; // whatever the provider returned — shape follows `prop`
|
|
19
|
+
prop: string; // echo
|
|
20
|
+
channel: string; // echo
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
`data` is deliberately untyped: each property returns whatever Meta (or the
|
|
25
|
+
local config) hands back. Pass a type argument when you know the shape.
|
|
26
|
+
|
|
27
|
+
## Which channels support which properties
|
|
28
|
+
|
|
29
|
+
| Category | Readable | Writable |
|
|
30
|
+
| --- | --- | --- |
|
|
31
|
+
| `whatsapp` | all | all |
|
|
32
|
+
| `rcs` | `fetch_channel_queue` `recharge_url` `token` | `test_message` |
|
|
33
|
+
| anything else | none | none |
|
|
34
|
+
|
|
35
|
+
Asking for an unsupported property is `400 unsupported_channel_prop`, with the
|
|
36
|
+
supported list in `details`.
|
|
37
|
+
|
|
38
|
+
## `setProp` options
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
interface SetPropOptions {
|
|
42
|
+
body?: Record<string, unknown>; // JSON body — ignored when `raw` is set
|
|
43
|
+
query?: Record<string, unknown>; // query string parameters
|
|
44
|
+
raw?: BodyInit; // file bytes, for the three binary properties
|
|
45
|
+
contentType?: string; // required whenever `raw` is set
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
**Three properties take file bytes instead of JSON**: `profile_pic`,
|
|
50
|
+
`resumable_upload` and `upload_media`. Pass them as `raw` with a `contentType`.
|
|
51
|
+
|
|
52
|
+
Several write properties wrap their payload in an `update` key — that is Meta's
|
|
53
|
+
own envelope, passed straight through. The table below says which.
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Readable properties
|
|
58
|
+
|
|
59
|
+
### `general`
|
|
60
|
+
|
|
61
|
+
Meta's general details for the phone number: verified name, quality rating,
|
|
62
|
+
display number status.
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
const { data } = await crm.channels.getProp(id, 'general');
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### `business_profile`
|
|
69
|
+
|
|
70
|
+
The WhatsApp business profile: about text, address, description, email,
|
|
71
|
+
websites, vertical, profile picture URL.
|
|
72
|
+
|
|
73
|
+
### `business_compliance`
|
|
74
|
+
|
|
75
|
+
Compliance information registered for the business (India's customer-care
|
|
76
|
+
details, and similar).
|
|
77
|
+
|
|
78
|
+
### `business_commerce`
|
|
79
|
+
|
|
80
|
+
Commerce settings: whether the catalog and cart are on.
|
|
81
|
+
|
|
82
|
+
### `conversation_automation`
|
|
83
|
+
|
|
84
|
+
The configured welcome message, ice-breaker prompts and commands.
|
|
85
|
+
|
|
86
|
+
### `qr_code`
|
|
87
|
+
|
|
88
|
+
The channel's WhatsApp QR codes and their short links.
|
|
89
|
+
|
|
90
|
+
### `message_analytics`
|
|
91
|
+
|
|
92
|
+
Meta's message analytics for the number.
|
|
93
|
+
|
|
94
|
+
| Query | Required | Default | Notes |
|
|
95
|
+
| --- | --- | --- | --- |
|
|
96
|
+
| `start` | yes | — | Unix seconds. |
|
|
97
|
+
| `end` | yes | — | Unix seconds. |
|
|
98
|
+
| `breakdown` | no | `'DAY'` | Meta's breakdown granularity. |
|
|
99
|
+
| `number` | no | the channel's own identity | Phone number to report on. |
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
const { data } = await crm.channels.getProp(id, 'message_analytics', {
|
|
103
|
+
start: 1767225600,
|
|
104
|
+
end: 1769817600,
|
|
105
|
+
breakdown: 'DAY',
|
|
106
|
+
});
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
A missing `start` or `end` is a `422`.
|
|
110
|
+
|
|
111
|
+
### `redirect_url`
|
|
112
|
+
|
|
113
|
+
A deep link into Meta Business Suite, prefilled for this channel.
|
|
114
|
+
|
|
115
|
+
| Query | Required | Values |
|
|
116
|
+
| --- | --- | --- |
|
|
117
|
+
| `target` | yes | `meta_catalog_manager` · `link_whatsApp_catalog` · `wa_manager` · `business` |
|
|
118
|
+
|
|
119
|
+
An unknown target is a `422` listing the valid ones. `data` is the URL string.
|
|
120
|
+
|
|
121
|
+
### `token`
|
|
122
|
+
|
|
123
|
+
The channel's inbound API token, followed by an encrypted copy: the prefix
|
|
124
|
+
identifies the channel, the remainder proves the platform issued it. `data` is
|
|
125
|
+
the concatenated string. This is how you read a token back after
|
|
126
|
+
[`rotateToken`](channels.md#rotatetoken).
|
|
127
|
+
|
|
128
|
+
### `channel_config`
|
|
129
|
+
|
|
130
|
+
What the agency permits on this channel. Agencies that have never saved a config
|
|
131
|
+
get permissive defaults.
|
|
132
|
+
|
|
133
|
+
```ts
|
|
134
|
+
{ about: boolean } // may the about text be edited
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### `fetch_channel_queue`
|
|
138
|
+
|
|
139
|
+
How many messages are waiting in the channel's outbound queue.
|
|
140
|
+
|
|
141
|
+
### `recharge_url`
|
|
142
|
+
|
|
143
|
+
The agency's hosted top-up page for the channel's currency, prefilled with the
|
|
144
|
+
caller's details. `404 recharge_link_unavailable` when no link is configured for
|
|
145
|
+
that currency.
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## Writable properties
|
|
150
|
+
|
|
151
|
+
### Profile and compliance
|
|
152
|
+
|
|
153
|
+
| `prop` | Body | Notes |
|
|
154
|
+
| --- | --- | --- |
|
|
155
|
+
| `business_profile` | `{ update: {...} }` | Meta's business profile fields. |
|
|
156
|
+
| `business_compliance` | `{ update: {...} }` | Compliance details. |
|
|
157
|
+
| `business_commerce` | `{ update: {...} }` | Catalog and cart settings. |
|
|
158
|
+
|
|
159
|
+
`update` is required on all three; leaving it out is a `422`.
|
|
160
|
+
|
|
161
|
+
```ts
|
|
162
|
+
await crm.channels.setProp(id, 'business_profile', {
|
|
163
|
+
body: { update: { about: 'We reply in minutes', email: 'hi@example.com' } },
|
|
164
|
+
});
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### `qr_code`
|
|
168
|
+
|
|
169
|
+
`{ update: {...} }`. Pass `update.edit: true` to change an existing code;
|
|
170
|
+
otherwise a new one is created.
|
|
171
|
+
|
|
172
|
+
### `conversation_automation`
|
|
173
|
+
|
|
174
|
+
Reads the current automation, changes one part of it, and writes it back — so
|
|
175
|
+
the rest survives.
|
|
176
|
+
|
|
177
|
+
| Body key | Effect |
|
|
178
|
+
| --- | --- |
|
|
179
|
+
| `welcome_message` | Sets `enable_welcome_message`. |
|
|
180
|
+
| `prop: 'prompts'` + `prompts` | Replaces the ice-breaker prompts. |
|
|
181
|
+
| `prop: 'commands'` + `commands` | Replaces the commands. |
|
|
182
|
+
|
|
183
|
+
```ts
|
|
184
|
+
await crm.channels.setProp(id, 'conversation_automation', {
|
|
185
|
+
body: { prop: 'prompts', prompts: [{ prompt_text: 'Track my order' }] },
|
|
186
|
+
});
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### `conversation_analytics`
|
|
190
|
+
|
|
191
|
+
| Body | Required | Default |
|
|
192
|
+
| --- | --- | --- |
|
|
193
|
+
| `range` | yes | — |
|
|
194
|
+
| `granularity` | no | `'DAILY'` |
|
|
195
|
+
| `breakdown` | no | `''` |
|
|
196
|
+
|
|
197
|
+
Meta's conversation statistics for the channel's own number.
|
|
198
|
+
|
|
199
|
+
### `whatsapp_business_encryption`
|
|
200
|
+
|
|
201
|
+
`{ business_public_key }` — required. Registers the key used for encrypted flow
|
|
202
|
+
responses.
|
|
203
|
+
|
|
204
|
+
### Number registration and migration
|
|
205
|
+
|
|
206
|
+
| `prop` | Body | What it does |
|
|
207
|
+
| --- | --- | --- |
|
|
208
|
+
| `wa_request_otp` | `{ method?: 'SMS' \| 'VOICE' }` | Asks Meta to send a verification code. Anything but `'VOICE'` is treated as `'SMS'`. |
|
|
209
|
+
| `wa_submit_otp` | `{ code }` | Verifies the code. Required. |
|
|
210
|
+
| `wa_pin_update` | `{ pin, localization? }` | Registers the number with Meta, which is what enables messaging on it. Runs the credit-line handshake and provisions the BSP account first. Already registered? The call is a no-op returning `{ pin: 'unchanged' }`. |
|
|
211
|
+
| `wa_biz_update` | — | For WA Biz numbers, already registered with Meta: does the billing handshake and activates the channel. `400 not_biz_app_channel` on any other channel. |
|
|
212
|
+
| `wa_deregister` | — | Deregisters the number at Meta. |
|
|
213
|
+
| `migrate_number` | `{ cc, phone_number }` | Both required. Moves the channel to a new number and rewrites its `phone_number_id` and identity. |
|
|
214
|
+
|
|
215
|
+
`wa_pin_update` and `wa_biz_update` both clear the channel's cache: registration
|
|
216
|
+
changes how the number behaves, and anything cached describes the state before
|
|
217
|
+
it.
|
|
218
|
+
|
|
219
|
+
### File uploads — **binary body**
|
|
220
|
+
|
|
221
|
+
| `prop` | Sends | Returns |
|
|
222
|
+
| --- | --- | --- |
|
|
223
|
+
| `profile_pic` | The image bytes | Uploads to Meta, then sets it as the profile picture. |
|
|
224
|
+
| `resumable_upload` | The file bytes | `{ h: '<handle>' }` — Meta's upload handle, for use in a template. |
|
|
225
|
+
| `upload_media` | The file bytes | Meta's media id, for sending. |
|
|
226
|
+
|
|
227
|
+
```ts
|
|
228
|
+
await crm.channels.setProp(id, 'profile_pic', {
|
|
229
|
+
raw: await file.arrayBuffer(),
|
|
230
|
+
contentType: 'image/jpeg',
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
const { data } = await crm.channels.setProp<{ h: string }>(id, 'resumable_upload', {
|
|
234
|
+
raw: bytes,
|
|
235
|
+
contentType: 'image/png',
|
|
236
|
+
});
|
|
237
|
+
data.h; // pass this handle to a template component
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
Up to 25 MB per request. A missing `contentType` will not be inferred.
|
|
241
|
+
|
|
242
|
+
### `test_message`
|
|
243
|
+
|
|
244
|
+
Sends one message on the channel, to check it end to end.
|
|
245
|
+
|
|
246
|
+
`409 channel_inactive` if the channel is not active.
|
|
247
|
+
|
|
248
|
+
For WhatsApp the body is an outbound message. `{ type: 'flow', flow_id, to, ... }`
|
|
249
|
+
sends a flow instead, and the flow must belong to this channel and be sendable.
|
|
250
|
+
`wait_for_flow_response` waits for the reply.
|
|
251
|
+
|
|
252
|
+
RCS speaks its own content schema: the whole body is passed through as the
|
|
253
|
+
message, with `queue_id` read off it.
|
|
254
|
+
|
|
255
|
+
```ts
|
|
256
|
+
await crm.channels.setProp(id, 'test_message', {
|
|
257
|
+
body: { to: '919876543210', type: 'text', text: { body: 'ping' } },
|
|
258
|
+
});
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
### `webhook_listener_update`
|
|
262
|
+
|
|
263
|
+
Points both Meta subscriptions — the WABA (account-level events) and the phone
|
|
264
|
+
number (messages) — at the platform's listener, and marks the channel as
|
|
265
|
+
upgraded. Both have to point there for the listener to be whole.
|
|
266
|
+
|
|
267
|
+
Returns `{ webhook: '<listener url>' }`.
|
|
268
|
+
|
|
269
|
+
### `delete_channel_queue`
|
|
270
|
+
|
|
271
|
+
Purges the channel's outbound queue. Returns `{ deleted: true }`. Anything
|
|
272
|
+
waiting is dropped, not sent.
|
|
273
|
+
|
|
274
|
+
### `enable_whatsapp_direct` / `disable_whatsapp_direct`
|
|
275
|
+
|
|
276
|
+
Flips `whatsapp_direct_enabled` on the channel. Takes no body; returns
|
|
277
|
+
`{ whatsapp_direct_enabled: boolean }`.
|
|
278
|
+
|
|
279
|
+
---
|
|
280
|
+
|
|
281
|
+
## Errors
|
|
282
|
+
|
|
283
|
+
| Status | Code | When |
|
|
284
|
+
| --- | --- | --- |
|
|
285
|
+
| 400 | `unsupported_channel_prop` | The channel's category does not support this property. `details.supported` lists what it does. |
|
|
286
|
+
| 400 | `provider_request_failed` | Meta rejected the call (a 4xx from them). `details` carries their reason. |
|
|
287
|
+
| 502 | `provider_request_failed` | Meta failed or was unreachable (a 5xx). |
|
|
288
|
+
| 502 | `partner_request_failed` | The BSP partner call failed. |
|
|
289
|
+
| 422 | — | A required body or query key was missing. |
|
|
290
|
+
| 404 | `not_found` | No such channel for this caller. |
|