@zavudev/sdk 0.59.1 → 0.60.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/CHANGELOG.md CHANGED
@@ -1,5 +1,28 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.60.1 (unreleased)
4
+
5
+ ### Documentation
6
+
7
+ * **calls:** `VoiceCall.cost` and the `call.completed` / `call.failed` webhook
8
+ descriptions say the cost can grow after the call ends: a premium model's
9
+ tokens are charged between 30 minutes and 24 hours later.
10
+
11
+ ## 0.60.0 (unreleased)
12
+
13
+ ### Features
14
+
15
+ * **v2/contacts:** `Contact.whatsapp` (`ContactWhatsapp`: `bsuid`, `username`),
16
+ regenerated from the spec, which already returned it.
17
+
18
+ ### Documentation
19
+
20
+ * **10dlc:** `TenDlcBrandCreateRequest.ein` is required to submit every entity
21
+ type except `SOLE_PROPRIETOR`, and a sole proprietor's `phone` must be a
22
+ mobile number. The `submit` `400` names the missing EIN.
23
+ * **webhooks:** `message.inbound` and `contact.identity_linked` descriptions
24
+ regenerated from the spec (BSUID, `contactId`, `conversationId`).
25
+
3
26
  ## 0.59.1 (unreleased)
4
27
 
5
28
  ### Documentation
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zavudev/sdk",
3
- "version": "0.59.1",
3
+ "version": "0.60.1",
4
4
  "description": "The official TypeScript library for the Zavudev API",
5
5
  "author": "Zavudev <hi@zavu.dev>",
6
6
  "types": "./index.d.ts",
package/src/types.ts CHANGED
@@ -926,11 +926,16 @@ export type TemplateCreateRequestButtonsItemOtpType = 'COPY_CODE' | 'ONE_TAP';
926
926
  * **Inbound events:**
927
927
  * - `message.inbound`: New message received from a contact. `data.conversationId`,
928
928
  * the inbox thread id (deep-link with
929
- * `https://dashboard.zavu.dev/{locale}/inbox?conv={conversationId}`), is sent on
930
- * **email** messages only, and is `null` there on the first message of a
931
- * brand-new thread. On every other channel it is absent: read it from `GET
932
- * /v1/messages/{messageId}`, which always has it, or from `conversation.new` for
933
- * a new contact. Reactions are delivered as `message.inbound` with
929
+ * `https://dashboard.zavu.dev/{locale}/inbox?conv={conversationId}`), is always
930
+ * set: the webhook is sent once the message has been filed in its thread, so the
931
+ * first message of a new thread carries the same id as `conversation.new`.
932
+ * `data.contactId` is the Zavu contact, and unlike `data.from` it does not
933
+ * change when a WhatsApp contact's identifier switches from a business-scoped
934
+ * user ID (BSUID) to a phone number, unless the switch reveals two contacts for
935
+ * one person; they are then merged and `contact.identity_linked` names the one
936
+ * that went away in `mergedContactId`. On WhatsApp, `data.whatsapp` carries the
937
+ * contact's `bsuid` and `username` when known. Reactions carry none of these
938
+ * three. Reactions are delivered as `message.inbound` with
934
939
  * `messageType='reaction'`. When the contact replied to (quoted) an earlier
935
940
  * message, `data.content` carries the reply context: `replyToMessageId`,
936
941
  * `replyToProviderMessageId`, `replyToFrom`, `replyToText`, and
@@ -956,6 +961,21 @@ export type TemplateCreateRequestButtonsItemOtpType = 'COPY_CODE' | 'ONE_TAP';
956
961
  * `https://dashboard.zavu.dev/{locale}/inbox?conv={conversationId}`), the
957
962
  * `phoneNumber` or `email` key, `channel`, `firstMessageId`, `firstMessageText`,
958
963
  * and `profileName`.
964
+ * - `contact.identity_linked`: A WhatsApp contact known by BSUID was tied to a
965
+ * phone number, a contact known by phone received a BSUID, or a contact changed
966
+ * their number. `data` carries `contactId`, `bsuid`, `phoneNumber` and `reason`:
967
+ * `provider_reported` (WhatsApp sent the phone and the BSUID together on an
968
+ * inbound message), `contact_shared` (the contact shared their phone number in
969
+ * reply to a `request_contact_info` message), `phone_changed` (the contact
970
+ * changed their number; `previousPhoneNumber` carries the old one, and `bsuid`
971
+ * is `null` when WhatsApp never reported one for the contact), or
972
+ * `bsuid_changed` (WhatsApp reported a new BSUID for the same number).
973
+ * `contactId` is the contact that remains if two were merged, and
974
+ * `mergedContactId` the one folded into it. `previousBsuid` is a BSUID you may
975
+ * have stored that no longer identifies the contact. Sent once per link, not on
976
+ * every message. `data.from` on `message.inbound` is the phone only when
977
+ * WhatsApp includes it, so it can still be the BSUID afterwards; key on
978
+ * `contactId`.
959
979
  * - `template.status_changed`: WhatsApp template approval status changed. `data`
960
980
  * carries `templateId`, `name`, `previousStatus`, `currentStatus`,
961
981
  * `rejectionReason`, and `category` — the category Meta currently bills the
@@ -986,9 +1006,10 @@ export type TemplateCreateRequestButtonsItemOtpType = 'COPY_CODE' | 'ONE_TAP';
986
1006
  * terminal events (`call.completed`, `call.failed`) additionally carry `cost` —
987
1007
  * what the call was billed, in USD, combining telephony and the managed voice
988
1008
  * pipeline — and `currency`. They are dispatched after the call is charged, so
989
- * `cost` is populated rather than zero; telephony can still be settling on an
990
- * outbound call, in which case `GET /v1/calls/{callId}` holds the reconciled
991
- * figure.
1009
+ * `cost` is populated rather than zero. It can still grow afterwards: telephony
1010
+ * can be settling on an outbound call, and on an agent that runs a premium model
1011
+ * the model's tokens are charged between 30 minutes and 24 hours after the call
1012
+ * ends. `GET /v1/calls/{callId}` holds the reconciled figure.
992
1013
  * - `call.initiated`: An outbound call was created and is dialing, or an inbound
993
1014
  * call was received. `data.status` = `ringing`
994
1015
  * - `call.answered`: The call was answered and the voice agent is connected.
@@ -1017,6 +1038,7 @@ export type WebhookEvent =
1017
1038
  | 'message.unsupported'
1018
1039
  | 'broadcast.status_changed'
1019
1040
  | 'conversation.new'
1041
+ | 'contact.identity_linked'
1020
1042
  | 'template.status_changed'
1021
1043
  | 'invitation.status_changed'
1022
1044
  | 'call.initiated'
@@ -1067,14 +1089,29 @@ export interface WebhookMessageData {
1067
1089
  messageType?: MessageType;
1068
1090
  /**
1069
1091
  * ID of the conversation (inbox thread) this message belongs to. Sent on
1070
- * `message.inbound` for **email** messages only; on SMS, WhatsApp, Telegram,
1071
- * Instagram and Messenger it is absent. Use it to deep-link to the dashboard
1072
- * inbox: `https://dashboard.zavu.dev/{locale}/inbox?conv={conversationId}`. On
1073
- * email it is `null` on the first message of a brand-new thread. In every case,
1074
- * fetch it via `GET /v1/messages/{messageId}`, whose `conversationId` is always
1075
- * populated, or take it from the `conversation.new` event for a new contact.
1092
+ * `message.inbound` on every channel, except for reactions. Use it to deep-link to
1093
+ * the dashboard inbox:
1094
+ * `https://dashboard.zavu.dev/{locale}/inbox?conv={conversationId}`. The webhook
1095
+ * is sent once the message has been filed in its thread, so this is always the
1096
+ * thread the message is in: on the first message of a new thread it is the id
1097
+ * `conversation.new` carries, and on the WhatsApp message that ties a BSUID to a
1098
+ * phone number it is the thread that remains once the two are joined.
1076
1099
  */
1077
1100
  conversationId?: string | null;
1101
+ /**
1102
+ * ID of the Zavu contact that sent the message. Sent on `message.inbound` on every
1103
+ * channel, except for reactions. It does not change when a WhatsApp contact's
1104
+ * `from` switches from a business-scoped user ID (BSUID) to a phone number, so use
1105
+ * it, not `from`, as your key for the person. `null` only when no contact could be
1106
+ * resolved.
1107
+ */
1108
+ contactId?: string | null;
1109
+ /**
1110
+ * WhatsApp identity of the sender. Present on WhatsApp `message.inbound` when Zavu
1111
+ * knows the sender's business-scoped user ID (BSUID), and absent otherwise. Same
1112
+ * shape as `whatsapp` on a contact or a conversation.
1113
+ */
1114
+ whatsapp?: WebhookMessageDataWhatsapp;
1078
1115
  /**
1079
1116
  * Text body or media caption. Present on `message.inbound` when the message has
1080
1117
  * text.
@@ -1102,6 +1139,16 @@ export interface WebhookMessageData {
1102
1139
  referral?: AdReferral;
1103
1140
  }
1104
1141
 
1142
+ export interface WebhookMessageDataWhatsapp {
1143
+ /**
1144
+ * Business-scoped user ID. Stable for the person on your WhatsApp Business
1145
+ * Account, whether or not `from` is a phone number.
1146
+ */
1147
+ bsuid: string;
1148
+ /** The WhatsApp username, when the sender has one and Meta reported it. */
1149
+ username?: string;
1150
+ }
1151
+
1105
1152
  /** Webhook configuration for the sender. */
1106
1153
  export interface SenderWebhook {
1107
1154
  /** HTTPS URL that will receive webhook events. */
@@ -1374,6 +1421,13 @@ export interface Contact {
1374
1421
  countryCode?: string;
1375
1422
  /** Contact's WhatsApp profile name. Only available for WhatsApp contacts. */
1376
1423
  profileName?: string | null;
1424
+ /**
1425
+ * WhatsApp identity. Present when Zavu has a business-scoped user ID (BSUID) for
1426
+ * the contact, and absent otherwise. A contact with a username can be reached
1427
+ * under its BSUID, its phone number, or both; they are the same contact. Look one
1428
+ * up by BSUID with the `bsuid` query parameter of the list endpoint.
1429
+ */
1430
+ whatsapp?: ContactWhatsapp;
1377
1431
  /** List of available messaging channels for this contact. */
1378
1432
  availableChannels: Array<string>;
1379
1433
  /** Preferred channel for this contact. */
@@ -1387,6 +1441,13 @@ export interface Contact {
1387
1441
  updatedAt?: string;
1388
1442
  }
1389
1443
 
1444
+ export interface ContactWhatsapp {
1445
+ /** Business-scoped user ID. Can be used as `to` when sending. */
1446
+ bsuid: string;
1447
+ /** The WhatsApp username, when Meta reported one. */
1448
+ username?: string;
1449
+ }
1450
+
1390
1451
  export type ContactDefaultChannel =
1391
1452
  | 'sms'
1392
1453
  | 'whatsapp'
@@ -3112,12 +3173,19 @@ export interface TenDlcBrandCreateRequest {
3112
3173
  displayName: string;
3113
3174
  /** Legal company name. */
3114
3175
  companyName?: string;
3115
- /** Employer Identification Number (format: XX-XXXXXXX). */
3176
+ /**
3177
+ * Employer Identification Number (format: XX-XXXXXXX). Required to submit every
3178
+ * entity type except `SOLE_PROPRIETOR`: a brand without it can be created as a
3179
+ * draft, and `POST /v1/10dlc/brands/{brandId}/submit` refuses it with `400`.
3180
+ */
3116
3181
  ein?: string;
3117
3182
  firstName?: string;
3118
3183
  lastName?: string;
3119
3184
  email: string;
3120
- /** Contact phone in E.164 format. */
3185
+ /**
3186
+ * Contact phone in E.164 format. For a `SOLE_PROPRIETOR`, a mobile number: the
3187
+ * carrier texts a verification code to it.
3188
+ */
3121
3189
  phone: string;
3122
3190
  street: string;
3123
3191
  city: string;
@@ -3635,7 +3703,9 @@ export interface VoiceCall {
3635
3703
  transcript?: Array<VoiceCallTurn>;
3636
3704
  /**
3637
3705
  * Total cost of the call in USD, combining the managed voice pipeline per-minute
3638
- * charge and telephony. Available once the call has ended.
3706
+ * charge, telephony, and, on an agent that runs a premium model, that model's
3707
+ * tokens. Available once the call has ended. Tokens are charged between 30 minutes
3708
+ * and 24 hours after the call ends, so the figure can grow until then.
3639
3709
  */
3640
3710
  cost?: number | null;
3641
3711
  /** Arbitrary metadata you attached when creating the call. */
@@ -4767,6 +4837,13 @@ export interface ContactsListParams {
4767
4837
  * start a new pagination run when the search term changes.
4768
4838
  */
4769
4839
  search?: string;
4840
+ /**
4841
+ * Exact WhatsApp business-scoped user ID (e.g. `CO.13491208655302741918`). Returns
4842
+ * the contact that holds it, or an empty list. Combines with `phoneNumber` and
4843
+ * `tag` as a filter, and cannot be combined with `search` (400). A value that is
4844
+ * not shaped like a BSUID returns 400.
4845
+ */
4846
+ bsuid?: string;
4770
4847
  /**
4771
4848
  * Tag name. Repeatable: `?tag=vip&tag=chile` returns contacts carrying **every**
4772
4849
  * tag given, not any of them — the same rule the dashboard filter applies.
package/src/v2/types.ts CHANGED
@@ -652,6 +652,13 @@ export interface Contact {
652
652
  countryCode?: string;
653
653
  /** Contact's WhatsApp profile name. Only available for WhatsApp contacts. */
654
654
  profileName?: string | null;
655
+ /**
656
+ * WhatsApp identity. Present when Zavu has a business-scoped user ID (BSUID) for
657
+ * the contact, and absent otherwise. A contact with a username can be reached
658
+ * under its BSUID, its phone number, or both; they are the same contact. Look one
659
+ * up by BSUID with the `bsuid` query parameter of the list endpoint.
660
+ */
661
+ whatsapp?: ContactWhatsapp;
655
662
  /** List of available messaging channels for this contact. */
656
663
  availableChannels: Array<string>;
657
664
  /** Preferred channel for this contact. */
@@ -665,6 +672,13 @@ export interface Contact {
665
672
  updatedAt?: string;
666
673
  }
667
674
 
675
+ export interface ContactWhatsapp {
676
+ /** Business-scoped user ID. Can be used as `to` when sending. */
677
+ bsuid: string;
678
+ /** The WhatsApp username, when Meta reported one. */
679
+ username?: string;
680
+ }
681
+
668
682
  export type ContactDefaultChannel =
669
683
  | 'sms'
670
684
  | 'whatsapp'
@@ -1684,11 +1698,16 @@ export interface TemplateSubmitRequest {
1684
1698
  * **Inbound events:**
1685
1699
  * - `message.inbound`: New message received from a contact. `data.conversationId`,
1686
1700
  * the inbox thread id (deep-link with
1687
- * `https://dashboard.zavu.dev/{locale}/inbox?conv={conversationId}`), is sent on
1688
- * **email** messages only, and is `null` there on the first message of a
1689
- * brand-new thread. On every other channel it is absent: read it from `GET
1690
- * /v2/messages/{messageId}`, which always has it, or from `conversation.new` for
1691
- * a new contact. Reactions are delivered as `message.inbound` with
1701
+ * `https://dashboard.zavu.dev/{locale}/inbox?conv={conversationId}`), is always
1702
+ * set: the webhook is sent once the message has been filed in its thread, so the
1703
+ * first message of a new thread carries the same id as `conversation.new`.
1704
+ * `data.contactId` is the Zavu contact, and unlike `data.from` it does not
1705
+ * change when a WhatsApp contact's identifier switches from a business-scoped
1706
+ * user ID (BSUID) to a phone number, unless the switch reveals two contacts for
1707
+ * one person; they are then merged and `contact.identity_linked` names the one
1708
+ * that went away in `mergedContactId`. On WhatsApp, `data.whatsapp` carries the
1709
+ * contact's `bsuid` and `username` when known. Reactions carry none of these
1710
+ * three. Reactions are delivered as `message.inbound` with
1692
1711
  * `messageType='reaction'`. When the contact replied to (quoted) an earlier
1693
1712
  * message, `data.content` carries the reply context: `replyToMessageId`,
1694
1713
  * `replyToProviderMessageId`, `replyToFrom`, `replyToText`, and
@@ -1714,6 +1733,21 @@ export interface TemplateSubmitRequest {
1714
1733
  * `https://dashboard.zavu.dev/{locale}/inbox?conv={conversationId}`), the
1715
1734
  * `phoneNumber` or `email` key, `channel`, `firstMessageId`, `firstMessageText`,
1716
1735
  * and `profileName`.
1736
+ * - `contact.identity_linked`: A WhatsApp contact known by BSUID was tied to a
1737
+ * phone number, a contact known by phone received a BSUID, or a contact changed
1738
+ * their number. `data` carries `contactId`, `bsuid`, `phoneNumber` and `reason`:
1739
+ * `provider_reported` (WhatsApp sent the phone and the BSUID together on an
1740
+ * inbound message), `contact_shared` (the contact shared their phone number in
1741
+ * reply to a `request_contact_info` message), `phone_changed` (the contact
1742
+ * changed their number; `previousPhoneNumber` carries the old one, and `bsuid`
1743
+ * is `null` when WhatsApp never reported one for the contact), or
1744
+ * `bsuid_changed` (WhatsApp reported a new BSUID for the same number).
1745
+ * `contactId` is the contact that remains if two were merged, and
1746
+ * `mergedContactId` the one folded into it. `previousBsuid` is a BSUID you may
1747
+ * have stored that no longer identifies the contact. Sent once per link, not on
1748
+ * every message. `data.from` on `message.inbound` is the phone only when
1749
+ * WhatsApp includes it, so it can still be the BSUID afterwards; key on
1750
+ * `contactId`.
1717
1751
  * - `template.status_changed`: WhatsApp template approval status changed. `data`
1718
1752
  * carries `templateId`, `name`, `previousStatus`, `currentStatus`,
1719
1753
  * `rejectionReason`, and `category` — the category Meta currently bills the
@@ -1743,9 +1777,10 @@ export interface TemplateSubmitRequest {
1743
1777
  * terminal events (`call.completed`, `call.failed`) additionally carry `cost` —
1744
1778
  * what the call was billed, in USD, combining telephony and the managed voice
1745
1779
  * pipeline — and `currency`. They are dispatched after the call is charged, so
1746
- * `cost` is populated rather than zero; telephony can still be settling on an
1747
- * outbound call, in which case `GET /v1/calls/{callId}` holds the reconciled
1748
- * figure.
1780
+ * `cost` is populated rather than zero. It can still grow afterwards: telephony
1781
+ * can be settling on an outbound call, and on an agent that runs a premium model
1782
+ * the model's tokens are charged between 30 minutes and 24 hours after the call
1783
+ * ends. `GET /v1/calls/{callId}` holds the reconciled figure.
1749
1784
  * - `call.initiated`: An outbound call was created and is dialing, or an inbound
1750
1785
  * call was received. `data.status` = `ringing`
1751
1786
  * - `call.answered`: The call was answered and the voice agent is connected.
@@ -1773,6 +1808,7 @@ export type WebhookEvent =
1773
1808
  | 'message.unsupported'
1774
1809
  | 'broadcast.status_changed'
1775
1810
  | 'conversation.new'
1811
+ | 'contact.identity_linked'
1776
1812
  | 'template.status_changed'
1777
1813
  | 'invitation.status_changed'
1778
1814
  | 'call.initiated'
@@ -1805,14 +1841,29 @@ export interface WebhookMessageData {
1805
1841
  messageType?: MessageType;
1806
1842
  /**
1807
1843
  * ID of the conversation (inbox thread) this message belongs to. Sent on
1808
- * `message.inbound` for **email** messages only; on SMS, WhatsApp, Telegram,
1809
- * Instagram and Messenger it is absent. Use it to deep-link to the dashboard
1810
- * inbox: `https://dashboard.zavu.dev/{locale}/inbox?conv={conversationId}`. On
1811
- * email it is `null` on the first message of a brand-new thread. In every case,
1812
- * fetch it via `GET /v2/messages/{messageId}`, whose `conversationId` is always
1813
- * populated, or take it from the `conversation.new` event for a new contact.
1844
+ * `message.inbound` on every channel, except for reactions. Use it to deep-link to
1845
+ * the dashboard inbox:
1846
+ * `https://dashboard.zavu.dev/{locale}/inbox?conv={conversationId}`. The webhook
1847
+ * is sent once the message has been filed in its thread, so this is always the
1848
+ * thread the message is in: on the first message of a new thread it is the id
1849
+ * `conversation.new` carries, and on the WhatsApp message that ties a BSUID to a
1850
+ * phone number it is the thread that remains once the two are joined.
1814
1851
  */
1815
1852
  conversationId?: string | null;
1853
+ /**
1854
+ * ID of the Zavu contact that sent the message. Sent on `message.inbound` on every
1855
+ * channel, except for reactions. It does not change when a WhatsApp contact's
1856
+ * `from` switches from a business-scoped user ID (BSUID) to a phone number, so use
1857
+ * it, not `from`, as your key for the person. `null` only when no contact could be
1858
+ * resolved.
1859
+ */
1860
+ contactId?: string | null;
1861
+ /**
1862
+ * WhatsApp identity of the sender. Present on WhatsApp `message.inbound` when Zavu
1863
+ * knows the sender's business-scoped user ID (BSUID), and absent otherwise. Same
1864
+ * shape as `whatsapp` on a contact or a conversation.
1865
+ */
1866
+ whatsapp?: WebhookMessageDataWhatsapp;
1816
1867
  /**
1817
1868
  * Text body or media caption. Present on `message.inbound` when the message has
1818
1869
  * text.
@@ -1840,6 +1891,16 @@ export interface WebhookMessageData {
1840
1891
  referral?: AdReferral;
1841
1892
  }
1842
1893
 
1894
+ export interface WebhookMessageDataWhatsapp {
1895
+ /**
1896
+ * Business-scoped user ID. Stable for the person on your WhatsApp Business
1897
+ * Account, whether or not `from` is a phone number.
1898
+ */
1899
+ bsuid: string;
1900
+ /** The WhatsApp username, when the sender has one and Meta reported it. */
1901
+ username?: string;
1902
+ }
1903
+
1843
1904
  /**
1844
1905
  * What a `POST` to your /v2 webhook carries. Signed like every Zavu webhook
1845
1906
  * (`X-Zavu-Signature`, with the webhook's `signatureVersion`); the event name is
@@ -2323,6 +2384,13 @@ export interface ConversationsListMessagesParams {
2323
2384
  /** Params for `contacts.list`. */
2324
2385
  export interface ContactsListParams {
2325
2386
  search?: string;
2387
+ /**
2388
+ * Exact WhatsApp business-scoped user ID (e.g. `CO.13491208655302741918`). Returns
2389
+ * the contact that holds it, or an empty list. Combines with `phoneNumber` and
2390
+ * `tag` as a filter, and cannot be combined with `search` (400). A value that is
2391
+ * not shaped like a BSUID returns 400.
2392
+ */
2393
+ bsuid?: string;
2326
2394
  tag?: Array<string>;
2327
2395
  /** @defaultValue `50` */
2328
2396
  limit?: number;
package/src/version.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  // Generated by zavu-codegen from apps/docs/openapi.json. Do not edit: run `bun run codegen`.
2
2
 
3
- export const VERSION = '0.59.1'; // x-release-please-version
3
+ export const VERSION = '0.60.1'; // x-release-please-version
package/types.d.mts CHANGED
@@ -844,11 +844,16 @@ export type TemplateCreateRequestButtonsItemOtpType = 'COPY_CODE' | 'ONE_TAP';
844
844
  * **Inbound events:**
845
845
  * - `message.inbound`: New message received from a contact. `data.conversationId`,
846
846
  * the inbox thread id (deep-link with
847
- * `https://dashboard.zavu.dev/{locale}/inbox?conv={conversationId}`), is sent on
848
- * **email** messages only, and is `null` there on the first message of a
849
- * brand-new thread. On every other channel it is absent: read it from `GET
850
- * /v1/messages/{messageId}`, which always has it, or from `conversation.new` for
851
- * a new contact. Reactions are delivered as `message.inbound` with
847
+ * `https://dashboard.zavu.dev/{locale}/inbox?conv={conversationId}`), is always
848
+ * set: the webhook is sent once the message has been filed in its thread, so the
849
+ * first message of a new thread carries the same id as `conversation.new`.
850
+ * `data.contactId` is the Zavu contact, and unlike `data.from` it does not
851
+ * change when a WhatsApp contact's identifier switches from a business-scoped
852
+ * user ID (BSUID) to a phone number, unless the switch reveals two contacts for
853
+ * one person; they are then merged and `contact.identity_linked` names the one
854
+ * that went away in `mergedContactId`. On WhatsApp, `data.whatsapp` carries the
855
+ * contact's `bsuid` and `username` when known. Reactions carry none of these
856
+ * three. Reactions are delivered as `message.inbound` with
852
857
  * `messageType='reaction'`. When the contact replied to (quoted) an earlier
853
858
  * message, `data.content` carries the reply context: `replyToMessageId`,
854
859
  * `replyToProviderMessageId`, `replyToFrom`, `replyToText`, and
@@ -874,6 +879,21 @@ export type TemplateCreateRequestButtonsItemOtpType = 'COPY_CODE' | 'ONE_TAP';
874
879
  * `https://dashboard.zavu.dev/{locale}/inbox?conv={conversationId}`), the
875
880
  * `phoneNumber` or `email` key, `channel`, `firstMessageId`, `firstMessageText`,
876
881
  * and `profileName`.
882
+ * - `contact.identity_linked`: A WhatsApp contact known by BSUID was tied to a
883
+ * phone number, a contact known by phone received a BSUID, or a contact changed
884
+ * their number. `data` carries `contactId`, `bsuid`, `phoneNumber` and `reason`:
885
+ * `provider_reported` (WhatsApp sent the phone and the BSUID together on an
886
+ * inbound message), `contact_shared` (the contact shared their phone number in
887
+ * reply to a `request_contact_info` message), `phone_changed` (the contact
888
+ * changed their number; `previousPhoneNumber` carries the old one, and `bsuid`
889
+ * is `null` when WhatsApp never reported one for the contact), or
890
+ * `bsuid_changed` (WhatsApp reported a new BSUID for the same number).
891
+ * `contactId` is the contact that remains if two were merged, and
892
+ * `mergedContactId` the one folded into it. `previousBsuid` is a BSUID you may
893
+ * have stored that no longer identifies the contact. Sent once per link, not on
894
+ * every message. `data.from` on `message.inbound` is the phone only when
895
+ * WhatsApp includes it, so it can still be the BSUID afterwards; key on
896
+ * `contactId`.
877
897
  * - `template.status_changed`: WhatsApp template approval status changed. `data`
878
898
  * carries `templateId`, `name`, `previousStatus`, `currentStatus`,
879
899
  * `rejectionReason`, and `category` — the category Meta currently bills the
@@ -904,9 +924,10 @@ export type TemplateCreateRequestButtonsItemOtpType = 'COPY_CODE' | 'ONE_TAP';
904
924
  * terminal events (`call.completed`, `call.failed`) additionally carry `cost` —
905
925
  * what the call was billed, in USD, combining telephony and the managed voice
906
926
  * pipeline — and `currency`. They are dispatched after the call is charged, so
907
- * `cost` is populated rather than zero; telephony can still be settling on an
908
- * outbound call, in which case `GET /v1/calls/{callId}` holds the reconciled
909
- * figure.
927
+ * `cost` is populated rather than zero. It can still grow afterwards: telephony
928
+ * can be settling on an outbound call, and on an agent that runs a premium model
929
+ * the model's tokens are charged between 30 minutes and 24 hours after the call
930
+ * ends. `GET /v1/calls/{callId}` holds the reconciled figure.
910
931
  * - `call.initiated`: An outbound call was created and is dialing, or an inbound
911
932
  * call was received. `data.status` = `ringing`
912
933
  * - `call.answered`: The call was answered and the voice agent is connected.
@@ -924,7 +945,7 @@ export type TemplateCreateRequestButtonsItemOtpType = 'COPY_CODE' | 'ONE_TAP';
924
945
  * - `domain.failed`: A custom email domain failed verification or is partially
925
946
  * verified
926
947
  */
927
- export type WebhookEvent = 'message.queued' | 'message.sent' | 'message.delivered' | 'message.read' | 'message.status' | 'message.failed' | 'message.inbound' | 'message.unsupported' | 'broadcast.status_changed' | 'conversation.new' | 'template.status_changed' | 'invitation.status_changed' | 'call.initiated' | 'call.answered' | 'call.completed' | 'call.failed' | 'domain.verified' | 'domain.failed';
948
+ export type WebhookEvent = 'message.queued' | 'message.sent' | 'message.delivered' | 'message.read' | 'message.status' | 'message.failed' | 'message.inbound' | 'message.unsupported' | 'broadcast.status_changed' | 'conversation.new' | 'contact.identity_linked' | 'template.status_changed' | 'invitation.status_changed' | 'call.initiated' | 'call.answered' | 'call.completed' | 'call.failed' | 'domain.verified' | 'domain.failed';
928
949
  /** Webhook payload structure sent to your endpoint. */
929
950
  export interface WebhookPayload {
930
951
  /** Unique event identifier. */
@@ -967,14 +988,29 @@ export interface WebhookMessageData {
967
988
  messageType?: MessageType;
968
989
  /**
969
990
  * ID of the conversation (inbox thread) this message belongs to. Sent on
970
- * `message.inbound` for **email** messages only; on SMS, WhatsApp, Telegram,
971
- * Instagram and Messenger it is absent. Use it to deep-link to the dashboard
972
- * inbox: `https://dashboard.zavu.dev/{locale}/inbox?conv={conversationId}`. On
973
- * email it is `null` on the first message of a brand-new thread. In every case,
974
- * fetch it via `GET /v1/messages/{messageId}`, whose `conversationId` is always
975
- * populated, or take it from the `conversation.new` event for a new contact.
991
+ * `message.inbound` on every channel, except for reactions. Use it to deep-link to
992
+ * the dashboard inbox:
993
+ * `https://dashboard.zavu.dev/{locale}/inbox?conv={conversationId}`. The webhook
994
+ * is sent once the message has been filed in its thread, so this is always the
995
+ * thread the message is in: on the first message of a new thread it is the id
996
+ * `conversation.new` carries, and on the WhatsApp message that ties a BSUID to a
997
+ * phone number it is the thread that remains once the two are joined.
976
998
  */
977
999
  conversationId?: string | null;
1000
+ /**
1001
+ * ID of the Zavu contact that sent the message. Sent on `message.inbound` on every
1002
+ * channel, except for reactions. It does not change when a WhatsApp contact's
1003
+ * `from` switches from a business-scoped user ID (BSUID) to a phone number, so use
1004
+ * it, not `from`, as your key for the person. `null` only when no contact could be
1005
+ * resolved.
1006
+ */
1007
+ contactId?: string | null;
1008
+ /**
1009
+ * WhatsApp identity of the sender. Present on WhatsApp `message.inbound` when Zavu
1010
+ * knows the sender's business-scoped user ID (BSUID), and absent otherwise. Same
1011
+ * shape as `whatsapp` on a contact or a conversation.
1012
+ */
1013
+ whatsapp?: WebhookMessageDataWhatsapp;
978
1014
  /**
979
1015
  * Text body or media caption. Present on `message.inbound` when the message has
980
1016
  * text.
@@ -1001,6 +1037,15 @@ export interface WebhookMessageData {
1001
1037
  */
1002
1038
  referral?: AdReferral;
1003
1039
  }
1040
+ export interface WebhookMessageDataWhatsapp {
1041
+ /**
1042
+ * Business-scoped user ID. Stable for the person on your WhatsApp Business
1043
+ * Account, whether or not `from` is a phone number.
1044
+ */
1045
+ bsuid: string;
1046
+ /** The WhatsApp username, when the sender has one and Meta reported it. */
1047
+ username?: string;
1048
+ }
1004
1049
  /** Webhook configuration for the sender. */
1005
1050
  export interface SenderWebhook {
1006
1051
  /** HTTPS URL that will receive webhook events. */
@@ -1258,6 +1303,13 @@ export interface Contact {
1258
1303
  countryCode?: string;
1259
1304
  /** Contact's WhatsApp profile name. Only available for WhatsApp contacts. */
1260
1305
  profileName?: string | null;
1306
+ /**
1307
+ * WhatsApp identity. Present when Zavu has a business-scoped user ID (BSUID) for
1308
+ * the contact, and absent otherwise. A contact with a username can be reached
1309
+ * under its BSUID, its phone number, or both; they are the same contact. Look one
1310
+ * up by BSUID with the `bsuid` query parameter of the list endpoint.
1311
+ */
1312
+ whatsapp?: ContactWhatsapp;
1261
1313
  /** List of available messaging channels for this contact. */
1262
1314
  availableChannels: Array<string>;
1263
1315
  /** Preferred channel for this contact. */
@@ -1272,6 +1324,12 @@ export interface Contact {
1272
1324
  createdAt: string;
1273
1325
  updatedAt?: string;
1274
1326
  }
1327
+ export interface ContactWhatsapp {
1328
+ /** Business-scoped user ID. Can be used as `to` when sending. */
1329
+ bsuid: string;
1330
+ /** The WhatsApp username, when Meta reported one. */
1331
+ username?: string;
1332
+ }
1275
1333
  export type ContactDefaultChannel = 'sms' | 'whatsapp' | 'telegram' | 'email' | 'instagram' | 'messenger' | 'voice';
1276
1334
  export interface ContactUpdateRequest {
1277
1335
  /**
@@ -2791,12 +2849,19 @@ export interface TenDlcBrandCreateRequest {
2791
2849
  displayName: string;
2792
2850
  /** Legal company name. */
2793
2851
  companyName?: string;
2794
- /** Employer Identification Number (format: XX-XXXXXXX). */
2852
+ /**
2853
+ * Employer Identification Number (format: XX-XXXXXXX). Required to submit every
2854
+ * entity type except `SOLE_PROPRIETOR`: a brand without it can be created as a
2855
+ * draft, and `POST /v1/10dlc/brands/{brandId}/submit` refuses it with `400`.
2856
+ */
2795
2857
  ein?: string;
2796
2858
  firstName?: string;
2797
2859
  lastName?: string;
2798
2860
  email: string;
2799
- /** Contact phone in E.164 format. */
2861
+ /**
2862
+ * Contact phone in E.164 format. For a `SOLE_PROPRIETOR`, a mobile number: the
2863
+ * carrier texts a verification code to it.
2864
+ */
2800
2865
  phone: string;
2801
2866
  street: string;
2802
2867
  city: string;
@@ -3289,7 +3354,9 @@ export interface VoiceCall {
3289
3354
  transcript?: Array<VoiceCallTurn>;
3290
3355
  /**
3291
3356
  * Total cost of the call in USD, combining the managed voice pipeline per-minute
3292
- * charge and telephony. Available once the call has ended.
3357
+ * charge, telephony, and, on an agent that runs a premium model, that model's
3358
+ * tokens. Available once the call has ended. Tokens are charged between 30 minutes
3359
+ * and 24 hours after the call ends, so the figure can grow until then.
3293
3360
  */
3294
3361
  cost?: number | null;
3295
3362
  /** Arbitrary metadata you attached when creating the call. */
@@ -4266,6 +4333,13 @@ export interface ContactsListParams {
4266
4333
  * start a new pagination run when the search term changes.
4267
4334
  */
4268
4335
  search?: string;
4336
+ /**
4337
+ * Exact WhatsApp business-scoped user ID (e.g. `CO.13491208655302741918`). Returns
4338
+ * the contact that holds it, or an empty list. Combines with `phoneNumber` and
4339
+ * `tag` as a filter, and cannot be combined with `search` (400). A value that is
4340
+ * not shaped like a BSUID returns 400.
4341
+ */
4342
+ bsuid?: string;
4269
4343
  /**
4270
4344
  * Tag name. Repeatable: `?tag=vip&tag=chile` returns contacts carrying **every**
4271
4345
  * tag given, not any of them — the same rule the dashboard filter applies.