ruby-whatsapp 0.4.0 → 0.6.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.
Files changed (130) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +152 -0
  3. data/README.md +322 -617
  4. data/docs/README.md +76 -0
  5. data/docs/business_phone_number/README.md +243 -0
  6. data/docs/business_phone_number/account.md +170 -0
  7. data/docs/business_phone_number/contact_book.md +90 -0
  8. data/docs/business_phone_number/profile.md +220 -0
  9. data/docs/business_phone_number/username.md +157 -0
  10. data/docs/configuration.md +177 -0
  11. data/docs/errors.md +219 -0
  12. data/docs/media/README.md +161 -0
  13. data/docs/message_templates/README.md +206 -0
  14. data/docs/message_templates/authentication.md +107 -0
  15. data/docs/message_templates/carousel.md +103 -0
  16. data/docs/message_templates/components.md +215 -0
  17. data/docs/message_templates/library.md +98 -0
  18. data/docs/message_templates/limited_time_offer.md +94 -0
  19. data/docs/message_templates/responses.md +173 -0
  20. data/docs/message_templates/standard.md +138 -0
  21. data/docs/messages/README.md +212 -0
  22. data/docs/messages/address.md +86 -0
  23. data/docs/messages/audio.md +43 -0
  24. data/docs/messages/contacts.md +129 -0
  25. data/docs/messages/document.md +49 -0
  26. data/docs/messages/image.md +59 -0
  27. data/docs/messages/interactive.md +342 -0
  28. data/docs/messages/location.md +51 -0
  29. data/docs/messages/location_request.md +59 -0
  30. data/docs/messages/mark_message_as_read.md +84 -0
  31. data/docs/messages/reaction.md +59 -0
  32. data/docs/messages/sticker.md +40 -0
  33. data/docs/messages/template.md +193 -0
  34. data/docs/messages/text.md +55 -0
  35. data/docs/messages/video.md +41 -0
  36. data/docs/schedule/README.md +183 -0
  37. data/docs/subscribed_app/README.md +139 -0
  38. data/docs/webhooks/README.md +217 -0
  39. data/docs/webhooks/account_alerts.md +44 -0
  40. data/docs/webhooks/account_review_update.md +29 -0
  41. data/docs/webhooks/account_update.md +54 -0
  42. data/docs/webhooks/automatic_events.md +47 -0
  43. data/docs/webhooks/business_capability_update.md +40 -0
  44. data/docs/webhooks/business_username_updates.md +55 -0
  45. data/docs/webhooks/history.md +107 -0
  46. data/docs/webhooks/message_template_components_update.md +46 -0
  47. data/docs/webhooks/message_template_quality_update.md +49 -0
  48. data/docs/webhooks/message_template_status_update.md +55 -0
  49. data/docs/webhooks/messages.md +520 -0
  50. data/docs/webhooks/partner_solutions.md +35 -0
  51. data/docs/webhooks/payment_configuration_update.md +40 -0
  52. data/docs/webhooks/phone_number_name_update.md +44 -0
  53. data/docs/webhooks/phone_number_quality_update.md +40 -0
  54. data/docs/webhooks/security.md +43 -0
  55. data/docs/webhooks/smb_app_state_sync.md +91 -0
  56. data/docs/webhooks/smb_message_echoes.md +91 -0
  57. data/docs/webhooks/template_category_update.md +52 -0
  58. data/docs/webhooks/user_id_update.md +84 -0
  59. data/docs/webhooks/user_preferences.md +87 -0
  60. data/lib/ruby/whatsapp/business_phone_number/account/details.rb +149 -0
  61. data/lib/ruby/whatsapp/business_phone_number/account/get.rb +53 -0
  62. data/lib/ruby/whatsapp/business_phone_number/account/transport.rb +30 -0
  63. data/lib/ruby/whatsapp/business_phone_number/account/update.rb +76 -0
  64. data/lib/ruby/whatsapp/business_phone_number/account.rb +23 -0
  65. data/lib/ruby/whatsapp/business_phone_number/contact_book/delete.rb +87 -0
  66. data/lib/ruby/whatsapp/business_phone_number/contact_book/deletion.rb +50 -0
  67. data/lib/ruby/whatsapp/business_phone_number/contact_book.rb +25 -0
  68. data/lib/ruby/whatsapp/business_phone_number/profile/details.rb +105 -0
  69. data/lib/ruby/whatsapp/business_phone_number/profile/get.rb +56 -0
  70. data/lib/ruby/whatsapp/business_phone_number/profile/update.rb +148 -0
  71. data/lib/ruby/whatsapp/business_phone_number/profile/verticals.rb +54 -0
  72. data/lib/ruby/whatsapp/business_phone_number/profile.rb +22 -0
  73. data/lib/ruby/whatsapp/business_phone_number/response.rb +3 -3
  74. data/lib/ruby/whatsapp/business_phone_number/transport.rb +12 -12
  75. data/lib/ruby/whatsapp/business_phone_number/username/claim.rb +180 -0
  76. data/lib/ruby/whatsapp/business_phone_number/username/delete.rb +36 -0
  77. data/lib/ruby/whatsapp/business_phone_number/username/details.rb +66 -0
  78. data/lib/ruby/whatsapp/business_phone_number/username/get.rb +33 -0
  79. data/lib/ruby/whatsapp/business_phone_number/username/suggest.rb +37 -0
  80. data/lib/ruby/whatsapp/business_phone_number/username/suggestions.rb +50 -0
  81. data/lib/ruby/whatsapp/business_phone_number/username.rb +27 -0
  82. data/lib/ruby/whatsapp/business_phone_number.rb +20 -4
  83. data/lib/ruby/whatsapp/message_templates/button/request_contact_info.rb +66 -0
  84. data/lib/ruby/whatsapp/message_templates/button.rb +5 -2
  85. data/lib/ruby/whatsapp/message_templates/component_set.rb +28 -0
  86. data/lib/ruby/whatsapp/message_templates/response/paging.rb +2 -1
  87. data/lib/ruby/whatsapp/message_templates.rb +2 -6
  88. data/lib/ruby/whatsapp/messages/base.rb +47 -5
  89. data/lib/ruby/whatsapp/messages/interactive/request_contact_info.rb +59 -0
  90. data/lib/ruby/whatsapp/messages/interactive.rb +22 -4
  91. data/lib/ruby/whatsapp/messages/response/contacts.rb +22 -5
  92. data/lib/ruby/whatsapp/path_building.rb +33 -0
  93. data/lib/ruby/whatsapp/schedule/create.rb +101 -0
  94. data/lib/ruby/whatsapp/schedule/list.rb +50 -0
  95. data/lib/ruby/whatsapp/schedule/response/collection.rb +61 -0
  96. data/lib/ruby/whatsapp/schedule/response/created.rb +30 -0
  97. data/lib/ruby/whatsapp/schedule/response/node.rb +85 -0
  98. data/lib/ruby/whatsapp/schedule/response.rb +18 -0
  99. data/lib/ruby/whatsapp/schedule/statuses.rb +19 -0
  100. data/lib/ruby/whatsapp/schedule/transport.rb +24 -0
  101. data/lib/ruby/whatsapp/schedule.rb +15 -0
  102. data/lib/ruby/whatsapp/subscribed_app/transport.rb +6 -5
  103. data/lib/ruby/whatsapp/version.rb +1 -1
  104. data/lib/ruby/whatsapp/webhook/business_username_updates.rb +51 -0
  105. data/lib/ruby/whatsapp/webhook/change.rb +3 -1
  106. data/lib/ruby/whatsapp/webhook/contact.rb +26 -3
  107. data/lib/ruby/whatsapp/webhook/history/chunk.rb +50 -0
  108. data/lib/ruby/whatsapp/webhook/history/thread.rb +64 -0
  109. data/lib/ruby/whatsapp/webhook/history.rb +73 -34
  110. data/lib/ruby/whatsapp/webhook/message/base.rb +27 -2
  111. data/lib/ruby/whatsapp/webhook/message/contacts/contact.rb +15 -2
  112. data/lib/ruby/whatsapp/webhook/message/context.rb +23 -2
  113. data/lib/ruby/whatsapp/webhook/message/edit.rb +50 -0
  114. data/lib/ruby/whatsapp/webhook/message/revoke.rb +36 -0
  115. data/lib/ruby/whatsapp/webhook/message/system.rb +19 -2
  116. data/lib/ruby/whatsapp/webhook/message/unknown.rb +2 -2
  117. data/lib/ruby/whatsapp/webhook/message.rb +3 -0
  118. data/lib/ruby/whatsapp/webhook/messages.rb +4 -3
  119. data/lib/ruby/whatsapp/webhook/smb_app_state_sync/contact.rb +76 -0
  120. data/lib/ruby/whatsapp/webhook/smb_app_state_sync/state_sync.rb +18 -4
  121. data/lib/ruby/whatsapp/webhook/smb_app_state_sync.rb +33 -9
  122. data/lib/ruby/whatsapp/webhook/smb_message_echoes/echo.rb +60 -0
  123. data/lib/ruby/whatsapp/webhook/smb_message_echoes.rb +26 -11
  124. data/lib/ruby/whatsapp/webhook/status.rb +23 -2
  125. data/lib/ruby/whatsapp/webhook/user_id_update/entry.rb +56 -0
  126. data/lib/ruby/whatsapp/webhook/user_id_update/id_change.rb +35 -0
  127. data/lib/ruby/whatsapp/webhook/user_id_update.rb +61 -0
  128. data/lib/ruby/whatsapp/webhook/user_preferences/entry.rb +76 -0
  129. data/lib/ruby/whatsapp/webhook/user_preferences.rb +44 -23
  130. metadata +100 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8b4ff5c9b9ee2bea36b889af8a3d153bb2e166d233ad9bfc51e20f9ec19ab9e7
4
- data.tar.gz: 8d419f24aae3b21d0218bb95c68c4f75dec56ebdafb4c0447d7ec29e8def7b7b
3
+ metadata.gz: 65b698016d0bde6643f240f8e78fc598c3ad7bd2f69a0ec6e001d02f2601e565
4
+ data.tar.gz: 99d27c447f3b17c55bfc4f4f916d1a22a4fcd5572e2bc5d96ef4f7a737463ae1
5
5
  SHA512:
6
- metadata.gz: 9c7d6fbfe3783788238af4ec52c683a122099f85fd7c408d94e09bc9a672b5594167e2de6bc05dfb85148f517bfee8c159d7f3f579191ae66020d05f3797cac5
7
- data.tar.gz: 0d55554385a2fb98318996292131a59c64bed7206766d3b20d7c3033077232e02d5a24878db7e31b12e66b35270698ad892f1e0759468ae656baeda39e271f92
6
+ metadata.gz: 57d5e745fa4255d5bb6125111ad18059b9325c6121eaa1b41482fc9b318e230e2d801cde7e760a4c7fe37922aa8c72fd0a38143f2b7daed91921fdeef98a8707
7
+ data.tar.gz: df83a3dd120820a8b3ca71986ed5d41bd66487805a1e0fcf5be4ca478be7c0d2aac97ce2b0726e58c438988629de41e213dff093b1db5e3e099c78b73b5ab9d9
data/CHANGELOG.md CHANGED
@@ -1,3 +1,155 @@
1
+ ## [0.6.1]
2
+
3
+ ### Added
4
+
5
+ - **`Whatsapp::Schedule`** — `List` and `Create` wrap a WhatsApp Business Account's
6
+ campaign schedules (`/{waba_id}/schedules`): an approved template sent to an audience
7
+ at a Unix `delivery_time`. `Create` checks all six documented-required fields before
8
+ the request, and requires `delivery_time` to be an integer. It checks nothing else,
9
+ since Meta publishes no length limits and no rule that the time must be in the future.
10
+ `List` takes `fields`, `limit`, `after`, and `before`, and returns an `Enumerable`
11
+ `Response::Collection` of `Response::Node`s with `next_cursor`. Its paging reuses
12
+ `MessageTemplates::Response::Paging` rather than duplicating it. `Node#status` stays
13
+ the raw string, with `Statuses` constants and `scheduled?`/`sending?`/`completed?`/
14
+ `failed?` for comparison. Meta's default `fields` omit `id`, so
15
+ `List::Fields::ALL` is offered to ask for it. Every failure raises the new
16
+ `Whatsapp::Schedule::Error`.
17
+
18
+ ## [0.6.0] - 2026-10-20
19
+
20
+ Completes the Business-Scoped User ID (BSUID) work that 0.5.0 began. 0.5.0 covered the
21
+ inbound webhook half; this covers the send side, the four webhook fields Meta's BSUID
22
+ documentation changed but 0.5.0 did not touch, the two new phone-number APIs, and the
23
+ button that asks a username adopter for their phone number back.
24
+
25
+ ### Fixed
26
+
27
+ - **`Whatsapp::Webhook::UserPreferences` and `::History` could never populate.** Both read
28
+ their payload one nesting level too high — `value["wa_id"]` and `value["metadata"]`,
29
+ where Meta nests entries under a repeat of the field's own name
30
+ (`value["user_preferences"][]`, `value["history"][]`), the convention `UserIdUpdate`
31
+ already followed. Every accessor on both classes returned `nil` or `[]` for a real
32
+ payload. The BSUID documentation is the first published example for either field, which
33
+ is what caught it; both specs now carry those payloads verbatim as regression guards.
34
+ **Breaking:** `UserPreferences`' `wa_id`/`detail`/`value`/`timestamp` move to
35
+ `change.value.preferences.first.*`, and `History#threads` moves to
36
+ `chunks.first.threads` — `#phase`/`#chunk_order`/`#progress` keep their names and now
37
+ actually work.
38
+ - **Corrected `Status#recipient_user_id`'s documented behavior.** The docs and one spec
39
+ claimed it appears only when you addressed a message to a BSUID. Per Meta's May 4, 2026
40
+ correction it is sent on _every_ status however the message was addressed, and omitted
41
+ only on a `failed` status for a message sent to a phone number. The deserializer was
42
+ always right; the claim around it was not. Also newly documented: the status webhook's
43
+ `contacts[]` accompanies `sent`/`delivered`/`read` but is omitted entirely for `failed`,
44
+ and `contacts[].profile.username` is omitted on `sent`.
45
+
46
+ ### Added
47
+
48
+ - **Send to a BSUID.** `Whatsapp::Messages::Base` gains `recipient` alongside `to`, so a
49
+ BSUID read off a webhook can be replied to without ever knowing the phone number. Supply
50
+ either or both — Meta gives `to` precedence — and at least one is required, which is now
51
+ a validation rather than a missing-keyword `ArgumentError`. Because all 13 message kinds
52
+ already forwarded with `super(**)`, and the `KINDS` factory splats `**payload`, this
53
+ reached every kind and `send_<kind>!` method without touching either. `envelope` compacts
54
+ the recipient pair only, so an omitted identifier is left out rather than sent as null.
55
+ `Messages::Response::Contacts` gains `user_id` and makes `wa_id` optional to match —
56
+ noting that Meta omits `user_id` whenever the message went to a phone number, _including_
57
+ when both identifiers were supplied, so a nil `user_id` does not mean the recipient has
58
+ no BSUID. The BSUID is deliberately not format-checked: its canonical source is this
59
+ gem's own webhook side, which keeps it a raw string, and rejecting on the way out what
60
+ was accepted on the way in would be the worst possible asymmetry.
61
+ - **`Whatsapp::BusinessPhoneNumber::Username`** — `Get`, `Claim`, `Delete`, and `Suggest`
62
+ wrap the searchable `@name` a WhatsApp user can find a number by. Four endpoints, three
63
+ wire shapes, two new value classes: `Username::Details` covers both `Get` and `Claim`
64
+ since `{status}` is a subset of `{username, status}`, `Username::Suggestions` owns the
65
+ doubly-wrapped suggestions envelope and is `Enumerable`, and `Delete` reuses the existing
66
+ `BusinessPhoneNumber::Response` for its bare `{success}`. Every one of Meta's published
67
+ username format rules is checked before the request, since the edge is rate limited — bar
68
+ the domain-suffix rule, whose published list ends in "and so on", so a suffix outside the
69
+ documented ten is left to the API. Mixed case is downcased rather than rejected, because
70
+ Meta compares usernames case-insensitively. `Details::Statuses` deliberately omits
71
+ `"deleted"`: that value reaches you only through the `business_username_updates` webhook,
72
+ and a write-side enum must not name a state no endpoint here can return.
73
+ - **`Whatsapp::BusinessPhoneNumber::ContactBook::Delete`** — removes a stored user contact
74
+ from the store Meta uses to keep putting a username adopter's phone number in your
75
+ webhooks. Returns its own `ContactBook::Deletion` rather than the shared `Response`,
76
+ because `deleted` is a second independent boolean that changes how `success` reads:
77
+ `success: true, deleted: false` means the request was fine and there was simply nothing
78
+ stored. Rejects a parent BSUID locally — the one BSUID-shape assertion in the gem, and
79
+ safe because it tests for a documented extra segment rather than a country-code grammar
80
+ Meta has never published. Both new namespaces reuse `BusinessPhoneNumber::Transport`
81
+ unchanged; there are still only two transports in that directory, three sub-namespaces
82
+ later.
83
+ - **`REQUEST_CONTACT_INFO`**, in both forms: a `MessageTemplates::Button` type for opening
84
+ a conversation with the ask, and a `Messages::Interactive` action kind
85
+ (`type: :request_contact_info`) for making it inside the 24-hour service window. Both
86
+ were previously recorded as deliberately absent for want of a published schema; the BSUID
87
+ documentation publishes one. The template button's `UTILITY`/`MARKETING` restriction lives
88
+ in `ComponentSet` alongside the other cross-component rules, and is checked by drilling
89
+ into the `BUTTONS` component rather than through `CATEGORY_RESTRICTED_TYPES`, which keys
90
+ on component types. The interactive kind is the first with nothing for a caller to
91
+ supply, so `ACTION_TYPES` gained a `parameterless` flag and `action:` is no longer
92
+ required for it.
93
+ - **Four more webhook fields brought up to their published schemas.**
94
+ `Whatsapp::Webhook::SmbMessageEchoes` gains a value-level `contacts` array and wraps each
95
+ echo in a new `SmbMessageEchoes::Echo` carrying `to`/`to_user_id`/`to_parent_user_id` —
96
+ a wrapper rather than three more accessors on `Message::Base`, because an inbound message
97
+ can never carry a recipient, and three permanently-nil readers on fifteen classes would
98
+ turn a helpful `NoMethodError` into a silent `nil`. **Breaking:** `echo.body` becomes
99
+ `echo.message.body`. `SmbAppStateSync` gains `messaging_product`/`metadata` and types its
100
+ synced contact as `SmbAppStateSync::Contact` (`full_name`, `first_name`, `phone_number`,
101
+ `user_id`, `parent_user_id`, `username`), with the per-entry `metadata.timestamp`
102
+ flattened onto the entry. **Breaking:** `sync.contact["phone_number"]` becomes
103
+ `sync.contact.phone_number`, and an entry with no contact returns `nil` rather than `{}`.
104
+ All four fields, plus `UserPreferences` and `History`, now expose `raw`.
105
+ - **`revoke` and `edit` message types**, registered in `Message::MESSAGE_TYPES` rather than
106
+ falling through to `Message::Unknown`. `Revoke` carries `original_message_id`; `Edit`
107
+ carries that plus a nested `message` recursed back through `Message.deserialize`, so an
108
+ edited image behaves exactly like an inbound one — content only, so the nested payload's
109
+ own `from`/`id`/`timestamp` read `nil`. Both also arrive through `smb_message_echoes`
110
+ when the business edits or revokes from the WhatsApp Business app.
111
+ - **`Message::Base#group_id`**, set when an inbound message came from a group chat.
112
+ Extracted once in `common_attributes`, so every message type has it. The matching
113
+ group-status identifiers on `Status` are deliberately _not_ added, and are now recorded
114
+ under a new "Not implemented" section in `lib/ruby/whatsapp/webhook/CLAUDE.md` along with
115
+ the `calls` and `group_participants_update` fields: they only appear on a status for a
116
+ message sent to a group, and this gem wraps no Groups API, so it can never receive one.
117
+
118
+ ## [0.5.0] - 2026-08-24
119
+
120
+ - Add Business-Scoped User ID (BSUID) support to inbound webhooks: Meta is rolling out
121
+ WhatsApp usernames, after which a user's phone number can be omitted from a webhook
122
+ entirely, leaving their BSUID (`user_id`, e.g. `"US.13491208655302741918"`) as the only
123
+ stable identifier. `Whatsapp::Webhook::Contact`, `Message::Base` (and therefore every
124
+ message type: `Text`, the `Media` family, `Location`, `Contacts`, `Interactive`,
125
+ `Button`, `Order`, `System`, `Reaction`, `Unknown`), and `Status` now all carry
126
+ `user_id`/`from_user_id`/`recipient_user_id`, alongside `parent_user_id` variants for
127
+ businesses enrolled in [parent BSUIDs](https://developers.facebook.com/documentation/business-messaging/whatsapp/business-scoped-user-ids#parent-business-scoped-user-ids).
128
+ `Contact` additionally gains `username`. `Message::Contacts::Contact` gains `vcard`/
129
+ `origin` for cards shared via a `REQUEST_CONTACT_INFO` button, and `Message::System`
130
+ gains its own `user_id`/`parent_user_id` for the customer's new BSUID after a
131
+ `user_changed_user_id` event. `Message::Context` carries `from_user_id`/
132
+ `from_parent_user_id` too, so a quoted reply still identifies its sender once the phone
133
+ number is gone. `from_user_id`/`from_parent_user_id` are extracted once in
134
+ `Message::Base.common_attributes`, so every message type gained them without any
135
+ per-type changes.
136
+ - Add two new confirmed webhook fields from the same BSUID rollout:
137
+ `Whatsapp::Webhook::UserIdUpdate` (a user's BSUID changed — composes the existing
138
+ `Metadata`/`Contact` plus `Entry`/`IdChange` for the `{previous, current}` change
139
+ records) and `Whatsapp::Webhook::BusinessUsernameUpdates` (a business phone number's
140
+ username moved between `approved`/`reserved`/`deleted`). Both also expose `raw`, the
141
+ untouched `value` hash, so a payload whose shape differs from Meta's published example
142
+ is still recoverable rather than silently deserializing to an empty object.
143
+
144
+ ## [0.4.2] - 2026-08-19
145
+
146
+ - Add WhatsApp Business Profile management: `Whatsapp::BusinessPhoneNumber::Profile::Get` reads the public card a WhatsApp user sees before they reply — about text, description, address, email, websites, industry vertical, and profile picture URL — and `::Profile::Update` writes them. Unlike `::Account`, this is not an exception to the module's `phone_id` rule: Meta exposes the profile as an edge on the phone number itself (`GET`/`POST /{phone_id}/whatsapp_business_profile`), so both actions reuse the existing `BusinessPhoneNumber::Transport#edge_path` unchanged — no new transport, no new ID to configure. `Update` validates the 21-value `Profile::Verticals` enum and Meta's documented limits (about 139, address 256, description 512, email 128 characters, at most 2 websites) before any request is made, and compacts omitted fields out of the body rather than sending null — which Meta reads as an instruction to blank them — with an empty `websites` array surviving on purpose, since that is how the list is cleared. `email`'s format is deliberately left to Meta, which validates it server-side; a regex here would reject valid exotic addresses. It reuses the existing `BusinessPhoneNumber::Response`, since the endpoint answers a bare `{success}`.
147
+ - `Profile::Details` owns Meta's `data` envelope so callers never reach through it. Meta's reference declares the entry as `{"business_profile": {...}}` while live responses are observed to carry the profile fields directly in `data[0]`; `deserialize` reads both rather than betting on one, following the same flatten-the-meaningless-wrapper habit as `SubscribedApp::Response::App` and `MessageTemplates::Response::Paging`. Its `vertical` stays a raw string, never validated or upcased on the way in, so a value Meta adds later still round-trips — compare it against `Verticals::ALL`.
148
+
149
+ ## [0.4.1] - 2026-08-18
150
+
151
+ - Add WhatsApp Business Account details and updates: `Whatsapp::BusinessPhoneNumber::Account::Get` reads the WABA node — name, timezone, message template namespace, country, and the review, verification, and ownership state — and `::Account::Update` renames the account or moves its timezone. `Get` returns an `Account::Details` carrying `ReviewStatuses`/`VerificationStatuses`/`OwnershipTypes` constants and `#approved?`/`#verified?`/`#self_owned?` predicates; statuses stay raw strings and are never validated on the way in, so a value Meta adds later still round-trips. `Update` validates locally that at least one of `name`/`timezone_id` is present and non-blank before any request is made, and reuses the existing `BusinessPhoneNumber::Response` since the endpoint answers a bare `{success}`. These two live under `BusinessPhoneNumber` but address `waba_id` rather than `phone_id` — the module's one account-scoped corner — so they carry their own transport.
152
+
1
153
  ## [0.4.0] - 2026-08-14
2
154
 
3
155
  - Add subscribed apps management: `Whatsapp::SubscribedApp::List`, `::Subscribe`, and `::Unsubscribe` wrap the `subscribed_apps` edge — the switch that turns a WABA's webhook delivery on or off, as opposed to `Whatsapp::Webhook`, which only deserializes notifications once Meta is already sending them. Responses expose `Collection`, `Subscription`, and `Unsubscription`, each composed from a shared `App` value object.