@funnelchat-hq/sdk 0.1.2 → 0.2.1
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 +112 -11
- package/dist/index.cjs +164 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +690 -96
- package/dist/index.d.ts +690 -96
- package/dist/index.js +162 -11
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ npm install @funnelchat-hq/sdk
|
|
|
16
16
|
|
|
17
17
|
## First call
|
|
18
18
|
|
|
19
|
-
You need an API key (`<id>|fc_live_...`) created in the "Developers" section of the panel, with the permissions of what you are going to use. Keep it in an environment variable: the client reads `FUNNELCHAT_API_KEY` when you do not pass it.
|
|
19
|
+
You need an API key (`<id>|fc_live_...`) created in the "Developers" section of the panel, with the permissions of what you are going to use. The panel groups them by area, with one checkbox per level: this README names each one as the panel shows it, with its literal in parentheses, such as **WhatsApp numbers → View** (`accounts:read`). The literal is what the panel shows as a tooltip and what the API names in its errors. In the panel, **Send** includes **View** and **Edit**, and **Edit** includes **View**. Keep it in an environment variable: the client reads `FUNNELCHAT_API_KEY` when you do not pass it.
|
|
20
20
|
|
|
21
21
|
```ts
|
|
22
22
|
import { Funnelchat } from '@funnelchat-hq/sdk';
|
|
@@ -24,7 +24,7 @@ import { Funnelchat } from '@funnelchat-hq/sdk';
|
|
|
24
24
|
|
|
25
25
|
const funnelchat = new Funnelchat({ apiKey: process.env.FUNNELCHAT_API_KEY });
|
|
26
26
|
|
|
27
|
-
// Your WhatsApp numbers (
|
|
27
|
+
// Your WhatsApp numbers. Permission: WhatsApp numbers → View (accounts:read). The `id` of each one is the `device_id` you send with.
|
|
28
28
|
for await (const device of funnelchat.devices.list()) {
|
|
29
29
|
console.log(device.id, device.number, device.capabilities.messages.templates);
|
|
30
30
|
}
|
|
@@ -56,14 +56,15 @@ Every request sends `Authorization: Bearer <key>`, `Accept: application/json` an
|
|
|
56
56
|
| `funnelchat.tags` | `list`, `create`, `retrieve`, `update`, `delete`, `listContacts`. |
|
|
57
57
|
| `funnelchat.customFields` | `list`, `create`, `retrieve`, `update`, `delete`. |
|
|
58
58
|
| `funnelchat.groups` | Read groups, participants, events and messages. |
|
|
59
|
-
| `funnelchat.devices` | `list`, `retrieve` (with `capabilities`), `update`, `status`, `health`, `me`, `checkPhone`, connection history. |
|
|
59
|
+
| `funnelchat.devices` | `list` (filter by `external_reference`), `create` (a QR number), `qr`, `retrieve` (with `capabilities`), `update`, `delete`, `status`, `health`, `me`, `checkPhone`, connection history. |
|
|
60
|
+
| `funnelchat.connectLinks` | `create`, `get`, `list`, `revoke`: a page where your customer connects a number. See [Connect a number](#connect-a-number). |
|
|
60
61
|
| `funnelchat.webhooks` | Subscriptions (`list`, `create`, `retrieve`, `update`, `delete`, `test`, `listDeliveries`), the catalog (`events`), `verify` and `constructEvent`. |
|
|
61
62
|
|
|
62
63
|
Parameters, bodies and responses are typed from the spec. For any other route, see [Any route](#any-route).
|
|
63
64
|
|
|
64
65
|
## Send a message
|
|
65
66
|
|
|
66
|
-
Sending with a key needs
|
|
67
|
+
Sending with a key needs **Contacts and conversations → Send** (`conversations:send`) and follows the WhatsApp rules. **Every send answers right away with the registered message, `status: 'queued'`:** accepted is not delivered. What happens next arrives by the `message.status_changed` webhook, or is read with `messages.get`.
|
|
67
68
|
|
|
68
69
|
**Open window: free text.** With an official Meta number you can only send free text if the contact wrote to you in the last 24 hours. Check the window first:
|
|
69
70
|
|
|
@@ -132,7 +133,7 @@ const { message } = await funnelchat.templates.send(3, '45', {
|
|
|
132
133
|
A WhatsApp form is built, published on one or more numbers and sent to a contact. There are two ids: `id` is one version; `family_id` is the form itself, the one you send it with and the one that comes back as `form.id` in `whatsapp_form.completed`.
|
|
133
134
|
|
|
134
135
|
```ts
|
|
135
|
-
const { data: draft } = await funnelchat.forms.create({ device_id: 3, template_key: 'lead' }); //
|
|
136
|
+
const { data: draft } = await funnelchat.forms.create({ device_id: 3, template_key: 'lead' }); // Contacts and conversations → Edit (conversations:write)
|
|
136
137
|
await funnelchat.forms.publish(draft.id);
|
|
137
138
|
|
|
138
139
|
const { message } = await funnelchat.messages.sendForm(2001, {
|
|
@@ -145,6 +146,105 @@ const { message } = await funnelchat.messages.sendForm(2001, {
|
|
|
145
146
|
|
|
146
147
|
The answers arrive by the `whatsapp_form.completed` webhook (`answers`: `key`, `label` and `value`) and are read with `forms.listResponses(familyId)`. `forms.templates()` lists the templates to start from; `forms.update(id, { definition, expected_updated_at })` saves a draft and answers `409 STALE` when someone saved it first.
|
|
147
148
|
|
|
149
|
+
## Connect a number
|
|
150
|
+
|
|
151
|
+
> **In production, except the official sign-up by link.** Creating QR numbers and connect links by QR work in production. Connect links with `type: 'meta'` are not available in production yet: they answer `503 meta_connect_disabled` until the Funnelchat domain is registered with Meta. The published spec does not describe these routes yet; see [Pending of the spec](#pending-of-the-spec).
|
|
152
|
+
|
|
153
|
+
A number belongs to the account of the key and takes a slot of its plan, as in the panel. Creating numbers, showing their QR code and creating or revoking connect links needs **WhatsApp numbers → Create, connect and delete** (`accounts:operate`); reading links, **WhatsApp numbers → View** (`accounts:read`). Tag each number with your own `external_reference` (for example the id of your customer): it comes back in the number, in the links and in every `device.*` webhook.
|
|
154
|
+
|
|
155
|
+
**A connect link** is a single-use page on the Funnelchat domain. Send your customer there: the number is created when they finish.
|
|
156
|
+
|
|
157
|
+
```ts
|
|
158
|
+
const { data: link } = await funnelchat.connectLinks.create(
|
|
159
|
+
{
|
|
160
|
+
type: 'qr', // a number by QR code; or 'meta' (not in production yet)
|
|
161
|
+
country: 'br', // the country of the phone that will scan the QR
|
|
162
|
+
external_reference: 'customer-81',
|
|
163
|
+
return_url: 'https://your-app.example/whatsapp/done', // optional, https only
|
|
164
|
+
expires_in_minutes: 1440, // 30 to 10080; 24 hours by default
|
|
165
|
+
},
|
|
166
|
+
{ idempotencyKey: 'connect-customer-81' },
|
|
167
|
+
);
|
|
168
|
+
console.log(link.url); // https://api.funnelchat.com/connect/… — it carries the token and is shown only now
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
- `country` (only `qr`, two letters, optional) is the country of the phone that will scan the QR: the WhatsApp session is opened from there, so send it when you know it. It does not go with `device_id` (a reconnection creates nothing), and the link returns it, lowercase.
|
|
172
|
+
- `type: 'meta'` (an official WhatsApp Cloud number, **not in production yet**: `503 meta_connect_disabled`) takes `allowed_connection_types: ['coexistence', 'dedicated']`, one or both; with both, your customer chooses on the page.
|
|
173
|
+
- `provision_us_number: true` (only `meta`) also offers a US number of the pool. A pool number is always dedicated: it needs `dedicated` in `allowed_connection_types` and does not go with `device_id`; otherwise the API answers `422 invalid_request` (the SDK does not check it for you).
|
|
174
|
+
- `device_id` reconnects a number you already have, without taking a new slot; `same_number_as` (only `qr`) adds QR to a number in coexistence.
|
|
175
|
+
- A Meta link tolerates retries: after a failure of Facebook or Meta it stays `pending`, so your customer can try again on the page; the fifth failed attempt turns it `failed` with `facebook_auth_failed` or `meta_error`. `quota_exceeded`, `trial_number_blocked` and `number_not_owned` close it at the first attempt.
|
|
176
|
+
- You learn the result three ways: the `device.connected` webhook (with `connect_link_id` and `external_reference`), the `return_url` (`?connect_link_id=…&status=completed|failed&device_id=…&error_code=…`), or reading the link:
|
|
177
|
+
|
|
178
|
+
```ts
|
|
179
|
+
const { data } = await funnelchat.connectLinks.get(link.id);
|
|
180
|
+
console.log(data.status, data.device_id, data.error_code); // 'completed', 9, null
|
|
181
|
+
|
|
182
|
+
for await (const open of funnelchat.connectLinks.list({ status: 'pending', external_reference: 'customer-81' })) {
|
|
183
|
+
await funnelchat.connectLinks.revoke(open.id); // only a pending link; any other answers 409
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
**A QR number, without a page.** Create it and show its QR yourself:
|
|
188
|
+
|
|
189
|
+
```ts
|
|
190
|
+
const { data: device } = await funnelchat.devices.create(
|
|
191
|
+
{ connection: 'qr', alias: 'Sales', country: 'br', external_reference: 'customer-81' },
|
|
192
|
+
{ idempotencyKey: 'qr-customer-81' },
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
const { data: qr } = await funnelchat.devices.qr(device.id);
|
|
196
|
+
// qr.qr_code is a PNG data URI (null once connected); ask again after qr.expires_in seconds
|
|
197
|
+
// (5 with a QR, 3 while the session starts, 0 once connected).
|
|
198
|
+
|
|
199
|
+
for await (const number of funnelchat.devices.list({ external_reference: 'customer-81' })) {
|
|
200
|
+
console.log(number.id, number.connection, number.connected);
|
|
201
|
+
}
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
- **Ask for the QR every 5 seconds and have it scanned right away.** WhatsApp changes the code every few seconds and an old one no longer links ("can't link the device now"): replace the image with the latest one on each call, and never store, cache or forward a QR.
|
|
205
|
+
- `country` (two letters, optional) is the country of the phone that will scan the QR: the WhatsApp session is opened from there.
|
|
206
|
+
- An account has at most one QR number waiting to be scanned: creating another answers `409 pending_number_exists`, with that number's `device_id` in `error.body`. Show its QR instead, or delete it.
|
|
207
|
+
- `external_reference` changes with `devices.update(id, { external_reference })`.
|
|
208
|
+
- `devices.delete(id)` deletes a number and frees its slot. It needs **WhatsApp numbers → Create, connect and delete** (`accounts:operate`) and is irreversible. An official number stops working through Funnelchat but stays registered with Meta; a QR one logs out. It sends `device.deleted`.
|
|
209
|
+
- `devices.list(query, options)` takes the query first, like every list. The signature of 0.1.2, `devices.list(options)`, still works.
|
|
210
|
+
|
|
211
|
+
**`idempotencyKey`** (both creates, 1 to 255 characters) is sent as `Idempotency-Key` and kept 24 hours. The same key with the same body answers the same response without creating another number or link, so after a timeout or a `5xx` you can repeat the call with it. Only a success is replayed, with the `Idempotent-Replayed: true` header (see it with `.withResponse()`); a rejection is evaluated again. On their own, creates follow the rule of writes (see [Retries](#retries)): they are retried only on a `429` or `503`, which guarantee nothing was created.
|
|
212
|
+
|
|
213
|
+
**The webhooks of numbers:**
|
|
214
|
+
|
|
215
|
+
| Event | `data` |
|
|
216
|
+
|---|---|
|
|
217
|
+
| `device.connected` | `device`, `connected_at`, `connect_link_id`. |
|
|
218
|
+
| `device.disconnected` | `device`, `disconnected_at`, `connect_link_id`, `reason`: `qr_logged_out`, `meta_account_not_registered`, `shadow_ban_protection`, `provider_error`, `disconnected_by_user` or `unknown`. |
|
|
219
|
+
| `device.deleted` | `device`, `deleted_at`, `connect_link_id`. |
|
|
220
|
+
| `device.health_changed` | `device`, `previous_state`, `state` (`available`, `limited`, `blocked`, `unknown`), `changed_at`, `connect_link_id`. Official numbers, only when the state changes. |
|
|
221
|
+
|
|
222
|
+
- `device` is `{ id, name, number, connection, external_reference }`, where `connection` is `qr`, `coexistence`, `dedicated` or `null`.
|
|
223
|
+
- `connect_link_id`: in `device.connected`, the connect link of that connection; in the other three, the link that last connected the number. `null` when it did not come from a link.
|
|
224
|
+
- What these events add to the ones production sends today (`connection`, `external_reference`, `connect_link_id` and `reason`) is optional in the types: it is missing until the API that serves it is deployed. Handle `undefined`.
|
|
225
|
+
|
|
226
|
+
**When it is refused** (`NUMBER_ERROR_CODES` and `IDEMPOTENCY_ERROR_CODES`):
|
|
227
|
+
|
|
228
|
+
| Error | What happened |
|
|
229
|
+
|---|---|
|
|
230
|
+
| `403 numbers_not_included` | The plan does not include numbers. |
|
|
231
|
+
| `403 number_limit_reached` | Every number of the plan is in use (`limit`, `used`, `pending_device_ids` in the body). |
|
|
232
|
+
| `409 pending_number_exists` | A QR number is already waiting to be scanned; the body brings its `device_id`. |
|
|
233
|
+
| `422 qr_requires_coexistence` | QR can only be added to an official number in coexistence. |
|
|
234
|
+
| `422 method_already_present` | The number already has that method; the body brings its `device_id`. |
|
|
235
|
+
| `422 invalid_reference_device` | `same_number_as` or `device_id` is not a number of yours that can take it. |
|
|
236
|
+
| `422 connection_type_mismatch` | A link with `device_id` of a number that connects another way than `type`. |
|
|
237
|
+
| `429 too_many_open_links` | 20 connect links are open. Revoke some. |
|
|
238
|
+
| `503 meta_connect_disabled` | Official numbers cannot be connected by link yet, as in production today. QR links work. |
|
|
239
|
+
| `409 link_not_pending` | Revoking a link that already ended. |
|
|
240
|
+
| `422 idempotency_key_mismatch` | The key was used with another body. |
|
|
241
|
+
| `422 invalid_idempotency_key` | The key is not 1 to 255 characters. |
|
|
242
|
+
| `409 operation_in_progress` | The first call with that key is still running. |
|
|
243
|
+
| `422 provider_unsupported` | `devices.qr` on an official number. |
|
|
244
|
+
| `409` on `devices.qr` | The number cannot connect now: `trial_number_blocked`, `number_suspended_by_plan`, `device_inactive`, `provisioning_quota_exceeded`. |
|
|
245
|
+
|
|
246
|
+
`NUMBER_ERROR_CODES` lists the rest (`connect_not_allowed`, `payment_required`, `provider_error`…), with their status. `isNumberQuotaError(error.code)` tells you whether it was the quota of the plan (the first five). `too_many_open_links` and `meta_connect_disabled` are never retried automatically.
|
|
247
|
+
|
|
148
248
|
## Receive webhooks with Express
|
|
149
249
|
|
|
150
250
|
Verify the signature over the **raw body**, exactly as it arrived. If you parse it and serialize it again, the signature changes: use `express.raw`, not `express.json`.
|
|
@@ -206,7 +306,7 @@ const subscription = await funnelchat.webhooks.create({
|
|
|
206
306
|
// Store subscription.id and subscription.secret: the secret is shown only once.
|
|
207
307
|
```
|
|
208
308
|
|
|
209
|
-
- Permissions: `webhooks:read` to read and `webhooks:write` for the rest. Also, a key can only subscribe to events of the areas it can read (`conversations:read
|
|
309
|
+
- Permissions: **Notifications (webhooks) → View** (`webhooks:read`) to read and **Notifications (webhooks) → Manage** (`webhooks:write`) for the rest. Also, a key can only subscribe to events of the areas it can read: **Contacts and conversations → View** (`conversations:read`), **Groups and campaigns → View** (`communities:read`) and **WhatsApp numbers → View** (`accounts:read`). Asking for others is a `422` on `events`.
|
|
210
310
|
- Subscriptions belong to the account: `list()` also brings the ones of other integrations. To change the URL use `update(id, …)` (the secret stays). `test(id)` sends a signed `webhook.test` right now and `listDeliveries(id)` is the history.
|
|
211
311
|
- `rotateSecret(id)` is **not open to API keys** (`403 route_not_available`): rotate the secret from the panel.
|
|
212
312
|
|
|
@@ -267,7 +367,7 @@ The errors of the API entry and of access control apply to any route (`ACCESS_ER
|
|
|
267
367
|
| Error | What happened |
|
|
268
368
|
|---|---|
|
|
269
369
|
| `401 unauthenticated` | The key is missing or not valid. |
|
|
270
|
-
| `403 permission_denied` | The key lacks the permission of the route. |
|
|
370
|
+
| `403 permission_denied` | The key lacks the permission of the route. `message` names it by its literal (such as `accounts:operate`), which the panel shows as the tooltip of each checkbox. |
|
|
271
371
|
| `403 route_not_available` | The route is not open to API keys (or it is turned off). |
|
|
272
372
|
| `404 route_not_found` | The route does not exist in the public API. |
|
|
273
373
|
| `429 daily_cap_exceeded` | The daily call cap of the key was reached. It carries `Retry-After`. |
|
|
@@ -285,7 +385,7 @@ const { data, requestId, response } = await funnelchat.contacts.retrieve(2001).w
|
|
|
285
385
|
|
|
286
386
|
- Reads and writes that give the same result when repeated (`GET`, `PUT`, `DELETE`) are retried on a `429`, a `5xx` or a network error (also when `timeoutMs` ran out), up to `maxRetries` times (2 by default).
|
|
287
387
|
- Other writes (`POST`, `PATCH`) are retried **only when the API guarantees they were not applied**: `429` and `503`. A `500`, `502` or `504`, a timeout or a dropped connection can come after the write was applied, so the error is thrown right away: read the state before repeating it.
|
|
288
|
-
- **A send is never retried automatically**: the `POST`s
|
|
388
|
+
- **A send is never retried automatically**: the `POST`s that need **Contacts and conversations → Send** (`conversations:send`): messages, forms, cards, forwards and templates. If a `429 rate_limited` sent nothing and you want the SDK to retry for you, ask for it in that call with `{ retrySend: true }`: it then follows the rule of writes. `mac_lockout_24h` and `mac_limit_reached` are never retried.
|
|
289
389
|
- With `Retry-After`, the wait is exactly what it asks for. If it asks for more than 60 seconds, there is no wait: the error is thrown right away with its `retryAfter` and you decide. Without `Retry-After`, the wait is exponential: 0.5 s, 1 s, 2 s… up to 8 s, with some jitter.
|
|
290
390
|
- Per call you can change `maxRetries`, `timeoutMs` and `signal`:
|
|
291
391
|
|
|
@@ -330,7 +430,8 @@ The published spec does not describe everything the API in production already ha
|
|
|
330
430
|
- what sends answer (the queued message), `messages.get`, `client_reference`, `file_type` and `file_name`;
|
|
331
431
|
- forms and `messages.sendForm`;
|
|
332
432
|
- the `capabilities` of numbers;
|
|
333
|
-
- webhook subscriptions and the events the spec does not bring (`message.status_changed` with its shape, `whatsapp_form.completed`, the group ones and the account ones)
|
|
433
|
+
- webhook subscriptions and the events the spec does not bring (`message.status_changed` with its shape, `whatsapp_form.completed`, the group ones and the account ones);
|
|
434
|
+
- creating and connecting numbers (`devices.create`, `devices.qr`, `country`, `external_reference`, `connectLinks`, `Idempotency-Key`) and the `device.*` events: in production (except `type: 'meta'` links), typed from the API of accounts until its spec is published.
|
|
334
435
|
|
|
335
436
|
`src/pending/` has what is not OpenAPI (`device_id` in `message.received`) and `src/core/error-codes.ts` the error codes.
|
|
336
437
|
|
|
@@ -348,7 +449,7 @@ FUNNELCHAT_SANDBOX_URL=http://127.0.0.1:4010 npm run test:e2e # against a runn
|
|
|
348
449
|
|
|
349
450
|
The SDK runs on Node 18 or later; the development tools (vitest 4, eslint 9) need Node 20 or later.
|
|
350
451
|
|
|
351
|
-
To release, push a `vX.Y.Z` tag on `main` matching the `version` in `package.json`; GitHub Actions (`.github/workflows/publish.yml`)
|
|
452
|
+
To release, push a `vX.Y.Z` tag on `main` matching the `version` in `package.json`; GitHub Actions publishes it through npm Trusted Publishing (`.github/workflows/publish.yml`), with provenance.
|
|
352
453
|
|
|
353
454
|
Everything generated comes from one script:
|
|
354
455
|
|
|
@@ -358,7 +459,7 @@ npm run generate # with the one in spec/, for
|
|
|
358
459
|
```
|
|
359
460
|
|
|
360
461
|
1. With `--spec`, it copies the spec to `spec/funnelchat.json` and writes `spec/procedencia.json`: repository, branch, commit, date and `sha256` of the file.
|
|
361
|
-
2. It adds `spec/pending.json`, but only what the spec does not bring, and lists what it already brings so you can delete it from there.
|
|
462
|
+
2. It adds `spec/pending.json`, but only what the spec does not bring, and lists what it already brings so you can delete it from there. An `operationPatches` entry without `while` adds body properties or `parameters` (such as a query filter) one by one, only while the operation lacks them.
|
|
362
463
|
3. It writes `src/generated/openapi.ts` (with [openapi-typescript](https://openapi-ts.dev)), `src/generated/routes.ts` (every operation, with area and permission) and `src/generated/events.ts` (the event catalog and the shape of the `data` of each one).
|
|
363
464
|
|
|
364
465
|
```
|
package/dist/index.cjs
CHANGED
|
@@ -319,11 +319,72 @@ var SEND_ERROR_CODES = {
|
|
|
319
319
|
/** 422: the number cannot send forms. */
|
|
320
320
|
number_not_eligible: 422
|
|
321
321
|
};
|
|
322
|
+
var NUMBER_ERROR_CODES = {
|
|
323
|
+
/** 403: the plan of the account does not include numbers. */
|
|
324
|
+
numbers_not_included: 403,
|
|
325
|
+
/** 403: the account already uses every number of its plan (`limit`, `used`, `pending_device_ids` in the body). */
|
|
326
|
+
number_limit_reached: 403,
|
|
327
|
+
/** 403: the account cannot connect numbers now. */
|
|
328
|
+
connect_not_allowed: 403,
|
|
329
|
+
/** 403: `devices.qr` of a number whose payment is due. */
|
|
330
|
+
payment_required: 403,
|
|
331
|
+
/**
|
|
332
|
+
* 409: the account already has a QR number waiting to be scanned (at most one): its `device_id`
|
|
333
|
+
* comes in the body. Show that one's QR, or delete it, instead of creating another.
|
|
334
|
+
*/
|
|
335
|
+
pending_number_exists: 409,
|
|
336
|
+
/** 422 (409 on `devices.qr`): QR can only be added to an official number that is in coexistence. */
|
|
337
|
+
qr_requires_coexistence: 422,
|
|
338
|
+
/** 422: the number already has that connection method; the body brings its `device_id`. */
|
|
339
|
+
method_already_present: 422,
|
|
340
|
+
/** 422: `same_number_as` or `device_id` is not a number of the account that can take it. */
|
|
341
|
+
invalid_reference_device: 422,
|
|
342
|
+
/** 422: a connect link with `device_id` of a number that connects another way than `type`. */
|
|
343
|
+
connection_type_mismatch: 422,
|
|
344
|
+
/** 422 (409 on `devices.qr`): the number is suspended because the plan has fewer numbers than the account uses. */
|
|
345
|
+
number_suspended_by_plan: 422,
|
|
346
|
+
/** 422: the body is not valid; `errors` says which fields. */
|
|
347
|
+
invalid_request: 422,
|
|
348
|
+
/** 409: `devices.qr` of a trial number that is blocked. */
|
|
349
|
+
trial_number_blocked: 409,
|
|
350
|
+
/** 409: `devices.qr` of a number that is turned off. */
|
|
351
|
+
device_inactive: 409,
|
|
352
|
+
/** 409: `devices.qr` when no more QR sessions can be started now. */
|
|
353
|
+
provisioning_quota_exceeded: 409,
|
|
354
|
+
/** 409: revoking a connect link that is no longer `pending`. */
|
|
355
|
+
link_not_pending: 409,
|
|
356
|
+
/** 429: the account has 20 connect links open. Revoke some, or wait for them to end. Not worth retrying. */
|
|
357
|
+
too_many_open_links: 429,
|
|
358
|
+
/** 500 (502 on `devices.qr`): the WhatsApp provider failed. */
|
|
359
|
+
provider_error: 500,
|
|
360
|
+
/** 503: official numbers (`type: 'meta'`) cannot be connected by link yet. QR links work. Not worth retrying. */
|
|
361
|
+
meta_connect_disabled: 503,
|
|
362
|
+
/** 403: the connect page was opened with an API key or a session. The page is for your customer, without credentials. */
|
|
363
|
+
credentials_not_allowed: 403
|
|
364
|
+
};
|
|
365
|
+
var IDEMPOTENCY_ERROR_CODES = {
|
|
366
|
+
/** 422: the key was already used with another body. Use a new key for a new resource. */
|
|
367
|
+
idempotency_key_mismatch: 422,
|
|
368
|
+
/** 422: the key is not 1 to 255 characters. */
|
|
369
|
+
invalid_idempotency_key: 422,
|
|
370
|
+
/** 409: the first call with that key, or another operation on the number, is still running. Read the state, or repeat it later. */
|
|
371
|
+
operation_in_progress: 409
|
|
372
|
+
};
|
|
322
373
|
var has = (table, code) => code !== void 0 && Object.prototype.hasOwnProperty.call(table, code);
|
|
323
374
|
function isSendRuleError(code) {
|
|
324
375
|
return has(SEND_ERROR_CODES, code);
|
|
325
376
|
}
|
|
326
|
-
var
|
|
377
|
+
var QUOTA_CODES = /* @__PURE__ */ new Set([
|
|
378
|
+
"numbers_not_included",
|
|
379
|
+
"number_limit_reached",
|
|
380
|
+
"pending_number_exists",
|
|
381
|
+
"qr_requires_coexistence",
|
|
382
|
+
"method_already_present"
|
|
383
|
+
]);
|
|
384
|
+
function isNumberQuotaError(code) {
|
|
385
|
+
return code !== void 0 && QUOTA_CODES.has(code);
|
|
386
|
+
}
|
|
387
|
+
var NOT_RETRYABLE_CODES = /* @__PURE__ */ new Set(["mac_lockout_24h", "mac_limit_reached", "too_many_open_links", "meta_connect_disabled"]);
|
|
327
388
|
|
|
328
389
|
// src/generated/routes.ts
|
|
329
390
|
var routes = [
|
|
@@ -376,6 +437,10 @@ var routes = [
|
|
|
376
437
|
{ "method": "GET", "path": "/api/v1/campaigns/{campaign}/pipelines/{pipeline}", "operationId": "campaign.showPipeline", "domain": "communities", "permission": "communities:read", "tag": "Grupos de una campa\xF1a y operaciones", "summary": "Ver un pipeline", "pending": false },
|
|
377
438
|
{ "method": "GET", "path": "/api/v1/campaigns/{campaign}/webhooks", "operationId": "campaignWebhook.index", "domain": "communities", "permission": "communities:read", "tag": "Webhooks de campa\xF1a (legado)", "summary": "Listar los webhooks de una campa\xF1a", "pending": false },
|
|
378
439
|
{ "method": "GET", "path": "/api/v1/campaigns/{campaign}/webhooks/logs", "operationId": "campaignWebhook.logs", "domain": "communities", "permission": "communities:read", "tag": "Webhooks de campa\xF1a (legado)", "summary": "Listar las entregas de los webhooks de una campa\xF1a", "pending": false },
|
|
440
|
+
{ "method": "GET", "path": "/api/v1/connect-links", "operationId": "pendingConnectLink.index", "domain": "accounts", "permission": "accounts:read", "tag": "ConnectLink", "summary": "List connect links", "pending": true },
|
|
441
|
+
{ "method": "POST", "path": "/api/v1/connect-links", "operationId": "pendingConnectLink.store", "domain": "accounts", "permission": "accounts:operate", "tag": "ConnectLink", "summary": "Create a connect link", "pending": true },
|
|
442
|
+
{ "method": "DELETE", "path": "/api/v1/connect-links/{link}", "operationId": "pendingConnectLink.destroy", "domain": "accounts", "permission": "accounts:operate", "tag": "ConnectLink", "summary": "Revoke a connect link", "pending": true },
|
|
443
|
+
{ "method": "GET", "path": "/api/v1/connect-links/{link}", "operationId": "pendingConnectLink.show", "domain": "accounts", "permission": "accounts:read", "tag": "ConnectLink", "summary": "Read a connect link", "pending": true },
|
|
379
444
|
{ "method": "PUT", "path": "/api/v1/contact-flows/{contactFlowId}/cancel", "operationId": "flow.cancelContactFlow", "domain": "conversations", "permission": "conversations:write", "tag": "Flow", "summary": "Cancelar un flujo en curso", "pending": false },
|
|
380
445
|
{ "method": "GET", "path": "/api/v1/contact-flows/{contactId}", "operationId": "flow.contactFlowHistory", "domain": "conversations", "permission": "conversations:read", "tag": "Flow", "summary": "Listar los flujos en curso de un contacto", "pending": false },
|
|
381
446
|
{ "method": "GET", "path": "/api/v1/contacts", "operationId": "contact.index", "domain": "conversations", "permission": "conversations:read", "tag": "Contact", "summary": "Listar contactos", "pending": false },
|
|
@@ -425,10 +490,11 @@ var routes = [
|
|
|
425
490
|
{ "method": "GET", "path": "/api/v1/custom-fields/{customField}", "operationId": "customField.show", "domain": "conversations", "permission": "conversations:read", "tag": "CustomField", "summary": "Ver un campo personalizado", "pending": false },
|
|
426
491
|
{ "method": "PUT", "path": "/api/v1/custom-fields/{customField}", "operationId": "customField.update", "domain": "conversations", "permission": "conversations:write", "tag": "CustomField", "summary": "Actualizar un campo personalizado", "pending": false },
|
|
427
492
|
{ "method": "POST", "path": "/api/v1/custom-fields/delete", "operationId": "customField.destroy", "domain": "conversations", "permission": "conversations:write", "tag": "CustomField", "summary": "Eliminar campos personalizados", "pending": false },
|
|
428
|
-
{ "method": "GET", "path": "/api/v1/devices", "operationId": "device.index", "domain": "accounts", "permission": "accounts:read", "tag": "Device", "summary": "Listar n\xFAmeros", "pending":
|
|
493
|
+
{ "method": "GET", "path": "/api/v1/devices", "operationId": "device.index", "domain": "accounts", "permission": "accounts:read", "tag": "Device", "summary": "Listar n\xFAmeros", "pending": true },
|
|
494
|
+
{ "method": "POST", "path": "/api/v1/devices", "operationId": "pendingDevice.store", "domain": "accounts", "permission": "accounts:operate", "tag": "Device", "summary": "Create a number connected by QR code", "pending": true },
|
|
429
495
|
{ "method": "DELETE", "path": "/api/v1/devices/{device}", "operationId": "device.destroy", "domain": "accounts", "permission": "accounts:operate", "tag": "Device", "summary": "Eliminar un n\xFAmero", "pending": false },
|
|
430
496
|
{ "method": "GET", "path": "/api/v1/devices/{device}", "operationId": "device.show", "domain": "accounts", "permission": "accounts:read", "tag": "Device", "summary": "Ver un n\xFAmero", "pending": false },
|
|
431
|
-
{ "method": "PUT", "path": "/api/v1/devices/{device}", "operationId": "device.update", "domain": "accounts", "permission": "accounts:write", "tag": "Device", "summary": "Actualizar un n\xFAmero", "pending":
|
|
497
|
+
{ "method": "PUT", "path": "/api/v1/devices/{device}", "operationId": "device.update", "domain": "accounts", "permission": "accounts:write", "tag": "Device", "summary": "Actualizar un n\xFAmero", "pending": true },
|
|
432
498
|
{ "method": "GET", "path": "/api/v1/devices/{device}/ad-groups", "operationId": "group.adGroups", "domain": "communities", "permission": "communities:read", "tag": "Grupos", "summary": "Listar los grupos de anuncios de un n\xFAmero", "pending": false },
|
|
433
499
|
{ "method": "POST", "path": "/api/v1/devices/{device}/capi/dataset", "operationId": "capiConnection.dataset", "domain": "accounts", "permission": "accounts:write", "tag": "CapiConnection", "summary": "Configurar el dataset de Conversions API", "pending": false },
|
|
434
500
|
{ "method": "GET", "path": "/api/v1/devices/{device}/capi/test", "operationId": "capiConnection.test", "domain": "accounts", "permission": "accounts:read", "tag": "CapiConnection", "summary": "Probar la conexi\xF3n con Conversions API", "pending": false },
|
|
@@ -452,6 +518,7 @@ var routes = [
|
|
|
452
518
|
{ "method": "DELETE", "path": "/api/v1/devices/{device}/message-queue/all", "operationId": "device.clearQueue", "domain": "accounts", "permission": "accounts:operate", "tag": "Device", "summary": "Vaciar la cola de mensajes", "pending": false },
|
|
453
519
|
{ "method": "GET", "path": "/api/v1/devices/{device}/message-queue/count", "operationId": "device.getQueueCount", "domain": "accounts", "permission": "accounts:read", "tag": "Device", "summary": "Contar los mensajes en la cola", "pending": false },
|
|
454
520
|
{ "method": "GET", "path": "/api/v1/devices/{device}/open-conversations", "operationId": "device.openConversations", "domain": "accounts", "permission": "accounts:read", "tag": "Device", "summary": "Ver qu\xE9 tel\xE9fonos tienen conversaci\xF3n abierta", "pending": false },
|
|
521
|
+
{ "method": "GET", "path": "/api/v1/devices/{device}/qr", "operationId": "pendingDevice.qr", "domain": "accounts", "permission": "accounts:operate", "tag": "Device", "summary": "The QR code to connect a number", "pending": true },
|
|
455
522
|
{ "method": "PUT", "path": "/api/v1/devices/{device}/reset", "operationId": "device.reset", "domain": "accounts", "permission": "accounts:operate", "tag": "Device", "summary": "Reiniciar la sesi\xF3n de un n\xFAmero", "pending": false },
|
|
456
523
|
{ "method": "GET", "path": "/api/v1/devices/{device}/status", "operationId": "device.status", "domain": "accounts", "permission": "accounts:read", "tag": "Device", "summary": "Ver el estado de conexi\xF3n", "pending": false },
|
|
457
524
|
{ "method": "DELETE", "path": "/api/v1/devices/{device}/templates/{template}", "operationId": "messageTemplate.delete", "domain": "conversations", "permission": "conversations:write", "tag": "MessageTemplate", "summary": "Eliminar una plantilla de un n\xFAmero", "pending": false },
|
|
@@ -676,6 +743,42 @@ var Resource = class {
|
|
|
676
743
|
get client() {
|
|
677
744
|
return this.#client;
|
|
678
745
|
}
|
|
746
|
+
/** The options of a request with `idempotencyKey` turned into the `Idempotency-Key` header. */
|
|
747
|
+
idempotent({ idempotencyKey, ...options } = {}) {
|
|
748
|
+
if (idempotencyKey === void 0) return options;
|
|
749
|
+
return { ...options, headers: { ...options.headers, "Idempotency-Key": idempotencyKey } };
|
|
750
|
+
}
|
|
751
|
+
};
|
|
752
|
+
|
|
753
|
+
// src/resources/connect-links.ts
|
|
754
|
+
var ConnectLinks = class extends Resource {
|
|
755
|
+
/**
|
|
756
|
+
* `POST /api/v1/connect-links`. `url` is the page to send your customer to: it carries the token
|
|
757
|
+
* and comes only in this response.
|
|
758
|
+
*
|
|
759
|
+
* `provision_us_number` (a US number of the pool, always dedicated) needs `dedicated` in
|
|
760
|
+
* `allowed_connection_types` and does not go with `device_id`: otherwise `422 invalid_request`.
|
|
761
|
+
* A Meta link stays `pending` after a failure of Facebook or Meta, so your customer can try again;
|
|
762
|
+
* the fifth failed attempt turns it `failed`.
|
|
763
|
+
*
|
|
764
|
+
* `country` (only `qr`, not with `device_id`) is the country of the phone that will scan the QR, in
|
|
765
|
+
* two letters: the WhatsApp session is opened from there. The link returns it, lowercase.
|
|
766
|
+
*/
|
|
767
|
+
create(body, options) {
|
|
768
|
+
return this.client.request("POST", "/api/v1/connect-links", { ...this.idempotent(options), body });
|
|
769
|
+
}
|
|
770
|
+
/** `GET /api/v1/connect-links/{link}`: its `status`, and `device_id` and `error_code` once it ends. */
|
|
771
|
+
get(link, options) {
|
|
772
|
+
return this.client.request("GET", "/api/v1/connect-links/{link}", { ...options, path: { link } });
|
|
773
|
+
}
|
|
774
|
+
/** `GET /api/v1/connect-links`, paginated, newest first: filter by `status` or `external_reference`. */
|
|
775
|
+
list(query, options) {
|
|
776
|
+
return this.client.paginate("/api/v1/connect-links", { ...options, query });
|
|
777
|
+
}
|
|
778
|
+
/** `DELETE /api/v1/connect-links/{link}`: revokes a `pending` link (any other is `409 link_not_pending`). */
|
|
779
|
+
revoke(link, options) {
|
|
780
|
+
return this.client.request("DELETE", "/api/v1/connect-links/{link}", { ...options, path: { link } });
|
|
781
|
+
}
|
|
679
782
|
};
|
|
680
783
|
|
|
681
784
|
// src/resources/contacts.ts
|
|
@@ -806,10 +909,31 @@ var CustomFields = class extends Resource {
|
|
|
806
909
|
};
|
|
807
910
|
|
|
808
911
|
// src/resources/devices.ts
|
|
912
|
+
var REQUEST_OPTION_KEYS = /* @__PURE__ */ new Set(["headers", "timeoutMs", "maxRetries", "retrySend", "signal"]);
|
|
913
|
+
function isRequestOptions(value) {
|
|
914
|
+
if (typeof value !== "object" || value === null) return false;
|
|
915
|
+
const keys = Object.keys(value);
|
|
916
|
+
return keys.length > 0 && keys.every((key) => REQUEST_OPTION_KEYS.has(key));
|
|
917
|
+
}
|
|
809
918
|
var Devices = class extends Resource {
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
return this.client.paginate("/api/v1/devices", { ...options });
|
|
919
|
+
list(queryOrOptions, options) {
|
|
920
|
+
if (options === void 0 && isRequestOptions(queryOrOptions)) return this.client.paginate("/api/v1/devices", { ...queryOrOptions });
|
|
921
|
+
return this.client.paginate("/api/v1/devices", { ...options, query: queryOrOptions });
|
|
922
|
+
}
|
|
923
|
+
/**
|
|
924
|
+
* `POST /api/v1/devices`: creates a number that connects by QR code (`connection: 'qr'`), with
|
|
925
|
+
* `connected: false`. Then show its QR with `qr`, or send your customer a connect link
|
|
926
|
+
* (`connectLinks.create`). Needs **WhatsApp numbers → Create, connect and delete**
|
|
927
|
+
* (`accounts:operate`). It takes a slot of the plan, with the panel's
|
|
928
|
+
* rejections: `403 numbers_not_included`, `403 number_limit_reached`, `422 qr_requires_coexistence`
|
|
929
|
+
* and `422 method_already_present` (`same_number_as` adds QR to a number in coexistence instead).
|
|
930
|
+
* An account has at most one QR number waiting to be scanned: another one is `409
|
|
931
|
+
* pending_number_exists`, with that number's `device_id` in `error.body`. `country` (two letters,
|
|
932
|
+
* such as `'br'`) is the country of the phone that will scan the QR: the WhatsApp session is opened
|
|
933
|
+
* from there.
|
|
934
|
+
*/
|
|
935
|
+
create(body, options) {
|
|
936
|
+
return this.client.request("POST", "/api/v1/devices", { ...this.idempotent(options), body });
|
|
813
937
|
}
|
|
814
938
|
/**
|
|
815
939
|
* `GET /api/v1/devices/{device}`. `capabilities` says what the number can do by API: message
|
|
@@ -818,10 +942,32 @@ var Devices = class extends Resource {
|
|
|
818
942
|
retrieve(device, options) {
|
|
819
943
|
return this.client.request("GET", "/api/v1/devices/{device}", { ...options, path: { device } });
|
|
820
944
|
}
|
|
821
|
-
/**
|
|
945
|
+
/**
|
|
946
|
+
* `DELETE /api/v1/devices/{device}`: deletes the number and frees its slot of the plan. An
|
|
947
|
+
* official number stops receiving through Funnelchat but stays registered with Meta; a QR one
|
|
948
|
+
* logs out. Irreversible; needs **WhatsApp numbers → Create, connect and delete**
|
|
949
|
+
* (`accounts:operate`). Sends `device.deleted`.
|
|
950
|
+
*/
|
|
951
|
+
delete(device, options) {
|
|
952
|
+
return this.client.request("DELETE", "/api/v1/devices/{device}", { ...options, path: { device } });
|
|
953
|
+
}
|
|
954
|
+
/** `PUT /api/v1/devices/{device}`: also changes `external_reference`. */
|
|
822
955
|
update(device, body, options) {
|
|
823
956
|
return this.client.request("PUT", "/api/v1/devices/{device}", { ...options, path: { device }, body });
|
|
824
957
|
}
|
|
958
|
+
/**
|
|
959
|
+
* `GET /api/v1/devices/{device}/qr`: the QR code to scan, as a PNG data URI (`qr_code`), or `null`
|
|
960
|
+
* once `connected`. Ask for it every `expires_in` seconds (5 with a QR, 3 while the session starts,
|
|
961
|
+
* 0 once connected) and have it scanned right away: WhatsApp changes the code every few seconds and
|
|
962
|
+
* an old one no longer links, so show the latest and never store or forward a QR. Only for QR
|
|
963
|
+
* numbers (an official one is `422 provider_unsupported`); needs
|
|
964
|
+
* **WhatsApp numbers → Create, connect and delete** (`accounts:operate`), since scanning it gives
|
|
965
|
+
* control of a WhatsApp. A number that cannot connect now
|
|
966
|
+
* answers `409` (`trial_number_blocked`, `number_suspended_by_plan`, `device_inactive`…).
|
|
967
|
+
*/
|
|
968
|
+
qr(device, options) {
|
|
969
|
+
return this.client.request("GET", "/api/v1/devices/{device}/qr", { ...options, path: { device } });
|
|
970
|
+
}
|
|
825
971
|
/** `GET /api/v1/devices/{device}/status`: connection status. */
|
|
826
972
|
status(device, options) {
|
|
827
973
|
return this.client.request("GET", "/api/v1/devices/{device}/status", { ...options, path: { device } });
|
|
@@ -1241,7 +1387,7 @@ var Webhooks = class extends Resource {
|
|
|
1241
1387
|
};
|
|
1242
1388
|
|
|
1243
1389
|
// src/version.ts
|
|
1244
|
-
var VERSION = "0.1
|
|
1390
|
+
var VERSION = "0.2.1";
|
|
1245
1391
|
|
|
1246
1392
|
// src/client.ts
|
|
1247
1393
|
var BASE_URLS = {
|
|
@@ -1268,6 +1414,8 @@ var Funnelchat = class {
|
|
|
1268
1414
|
customFields;
|
|
1269
1415
|
groups;
|
|
1270
1416
|
devices;
|
|
1417
|
+
/** Single-use pages where your customer connects a WhatsApp number to your account. */
|
|
1418
|
+
connectLinks;
|
|
1271
1419
|
/** Webhook subscriptions, plus `verify` and `constructEvent`. */
|
|
1272
1420
|
webhooks;
|
|
1273
1421
|
// Private fields: the key never shows up in `console.log(client)` or in JSON.
|
|
@@ -1302,6 +1450,7 @@ var Funnelchat = class {
|
|
|
1302
1450
|
this.customFields = new CustomFields(this);
|
|
1303
1451
|
this.groups = new Groups(this);
|
|
1304
1452
|
this.devices = new Devices(this);
|
|
1453
|
+
this.connectLinks = new ConnectLinks(this);
|
|
1305
1454
|
this.webhooks = new Webhooks(this);
|
|
1306
1455
|
}
|
|
1307
1456
|
/** What `JSON.stringify(client)` shows: the configuration, never the key. */
|
|
@@ -1453,8 +1602,10 @@ var webhookEvents = [
|
|
|
1453
1602
|
{ "type": "conversation.assigned", "domains": ["conversations"], "status": "live", "pending": false, "description": "Una conversaci\xF3n qued\xF3 asignada a una persona del equipo o a un agente de IA.\n\nSe env\xEDa cuando una conversaci\xF3n queda asignada a una persona del equipo (`assignee.type` = `human`) o a un agente de IA (`assignee.type` = `ai`), por cualquier camino. `assignee` trae el tipo y el nombre, nunca un identificador. No se env\xEDa al desasignar, ni cuando se vuelve a enviar el mismo asignado." },
|
|
1454
1603
|
{ "type": "conversation.closed", "domains": ["conversations"], "status": "live", "pending": false, "description": "Una conversaci\xF3n pas\xF3 a cerrada.\n\nSe env\xEDa cuando una conversaci\xF3n pasa de abierta a cerrada. No se env\xEDa si la conversaci\xF3n ya estaba cerrada ni cuando se reabre." },
|
|
1455
1604
|
{ "type": "daily_report.generated", "domains": ["communities"], "status": "live", "pending": true, "description": "The daily report of the account was generated (clicks, joins and leaves)." },
|
|
1456
|
-
{ "type": "device.connected", "domains": ["accounts"], "status": "live", "pending": true, "description": "One of the numbers of the account got connected: by scanning the QR code
|
|
1457
|
-
{ "type": "device.
|
|
1605
|
+
{ "type": "device.connected", "domains": ["accounts"], "status": "live", "pending": true, "description": "One of the numbers of the account got connected: by scanning the QR code, by finishing the sign-up of a WhatsApp Cloud number, or through a connect link." },
|
|
1606
|
+
{ "type": "device.deleted", "domains": ["accounts"], "status": "planned", "pending": true, "description": "A number of the account was deleted: its slot of the plan is free again." },
|
|
1607
|
+
{ "type": "device.disconnected", "domains": ["accounts"], "status": "live", "pending": true, "description": "One of the numbers of the account disconnected and can no longer send or receive: a QR session that closed or was protected from a shadow ban, an official number that failed its re-registration, or one disconnected from the panel." },
|
|
1608
|
+
{ "type": "device.health_changed", "domains": ["accounts"], "status": "planned", "pending": true, "description": "The health of an official number changed (what Meta lets it do). Emitted only when the overall state changes." },
|
|
1458
1609
|
{ "type": "flow.completed", "domains": ["conversations"], "status": "live", "pending": false, "description": "Un contacto termin\xF3 un flujo de automatizaci\xF3n.\n\nSe env\xEDa una sola vez por cada ejecuci\xF3n de un flujo, cuando termina con \xE9xito. No se env\xEDa si la ejecuci\xF3n se cort\xF3, se cancel\xF3, qued\xF3 bloqueada o fall\xF3. `contact` es `null` si la ejecuci\xF3n no tiene un contacto asociado. Un flujo muy usado puede generar muchos eventos seguidos." },
|
|
1459
1610
|
{ "type": "flow.failed", "domains": ["conversations"], "status": "live", "pending": false, "description": "La ejecuci\xF3n de un flujo para un contacto termin\xF3 por un error.\n\nSe env\xEDa una sola vez por cada ejecuci\xF3n de un flujo que termina por un error, no porque lleg\xF3 al final. `reason` indica el motivo y toma uno de estos valores: `node_missing` (el paso a ejecutar ya no existe), `unknown_node_type` (el paso es de un tipo no reconocido), `missing_subflow` (el flujo que deb\xEDa ejecutarse dentro de otro ya no existe), `malformed_node` (el paso tiene una configuraci\xF3n da\xF1ada), `loop_detected` (la ejecuci\xF3n dio vueltas sin esperar nada) y `execution_error` (un error inesperado detuvo la ejecuci\xF3n). No se env\xEDa si la ejecuci\xF3n se cancel\xF3, qued\xF3 bloqueada o se cort\xF3 porque se edit\xF3 el flujo o se unieron contactos. `contact` es `null` si la ejecuci\xF3n no tiene un contacto asociado." },
|
|
1460
1611
|
{ "type": "group_action.failed", "domains": ["communities"], "status": "live", "pending": true, "description": "An action on a group of a campaign failed." },
|
|
@@ -1505,7 +1656,9 @@ exports.DEFAULT_TIMEOUT_MS = DEFAULT_TIMEOUT_MS;
|
|
|
1505
1656
|
exports.DEFAULT_TOLERANCE_SECONDS = DEFAULT_TOLERANCE_SECONDS;
|
|
1506
1657
|
exports.Funnelchat = Funnelchat;
|
|
1507
1658
|
exports.FunnelchatError = FunnelchatError;
|
|
1659
|
+
exports.IDEMPOTENCY_ERROR_CODES = IDEMPOTENCY_ERROR_CODES;
|
|
1508
1660
|
exports.InternalServerError = InternalServerError;
|
|
1661
|
+
exports.NUMBER_ERROR_CODES = NUMBER_ERROR_CODES;
|
|
1509
1662
|
exports.NotFoundError = NotFoundError;
|
|
1510
1663
|
exports.Page = Page;
|
|
1511
1664
|
exports.PagePromise = PagePromise;
|
|
@@ -1520,6 +1673,7 @@ exports.VERSION = VERSION;
|
|
|
1520
1673
|
exports.WebhookVerificationError = WebhookVerificationError;
|
|
1521
1674
|
exports.constructEvent = constructEvent;
|
|
1522
1675
|
exports.default = Funnelchat;
|
|
1676
|
+
exports.isNumberQuotaError = isNumberQuotaError;
|
|
1523
1677
|
exports.isSendRuleError = isSendRuleError;
|
|
1524
1678
|
exports.routes = routes;
|
|
1525
1679
|
exports.verify = verify;
|