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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +152 -0
- data/README.md +322 -617
- data/docs/README.md +76 -0
- data/docs/business_phone_number/README.md +243 -0
- data/docs/business_phone_number/account.md +170 -0
- data/docs/business_phone_number/contact_book.md +90 -0
- data/docs/business_phone_number/profile.md +220 -0
- data/docs/business_phone_number/username.md +157 -0
- data/docs/configuration.md +177 -0
- data/docs/errors.md +219 -0
- data/docs/media/README.md +161 -0
- data/docs/message_templates/README.md +206 -0
- data/docs/message_templates/authentication.md +107 -0
- data/docs/message_templates/carousel.md +103 -0
- data/docs/message_templates/components.md +215 -0
- data/docs/message_templates/library.md +98 -0
- data/docs/message_templates/limited_time_offer.md +94 -0
- data/docs/message_templates/responses.md +173 -0
- data/docs/message_templates/standard.md +138 -0
- data/docs/messages/README.md +212 -0
- data/docs/messages/address.md +86 -0
- data/docs/messages/audio.md +43 -0
- data/docs/messages/contacts.md +129 -0
- data/docs/messages/document.md +49 -0
- data/docs/messages/image.md +59 -0
- data/docs/messages/interactive.md +342 -0
- data/docs/messages/location.md +51 -0
- data/docs/messages/location_request.md +59 -0
- data/docs/messages/mark_message_as_read.md +84 -0
- data/docs/messages/reaction.md +59 -0
- data/docs/messages/sticker.md +40 -0
- data/docs/messages/template.md +193 -0
- data/docs/messages/text.md +55 -0
- data/docs/messages/video.md +41 -0
- data/docs/schedule/README.md +183 -0
- data/docs/subscribed_app/README.md +139 -0
- data/docs/webhooks/README.md +217 -0
- data/docs/webhooks/account_alerts.md +44 -0
- data/docs/webhooks/account_review_update.md +29 -0
- data/docs/webhooks/account_update.md +54 -0
- data/docs/webhooks/automatic_events.md +47 -0
- data/docs/webhooks/business_capability_update.md +40 -0
- data/docs/webhooks/business_username_updates.md +55 -0
- data/docs/webhooks/history.md +107 -0
- data/docs/webhooks/message_template_components_update.md +46 -0
- data/docs/webhooks/message_template_quality_update.md +49 -0
- data/docs/webhooks/message_template_status_update.md +55 -0
- data/docs/webhooks/messages.md +520 -0
- data/docs/webhooks/partner_solutions.md +35 -0
- data/docs/webhooks/payment_configuration_update.md +40 -0
- data/docs/webhooks/phone_number_name_update.md +44 -0
- data/docs/webhooks/phone_number_quality_update.md +40 -0
- data/docs/webhooks/security.md +43 -0
- data/docs/webhooks/smb_app_state_sync.md +91 -0
- data/docs/webhooks/smb_message_echoes.md +91 -0
- data/docs/webhooks/template_category_update.md +52 -0
- data/docs/webhooks/user_id_update.md +84 -0
- data/docs/webhooks/user_preferences.md +87 -0
- data/lib/ruby/whatsapp/business_phone_number/account/details.rb +149 -0
- data/lib/ruby/whatsapp/business_phone_number/account/get.rb +53 -0
- data/lib/ruby/whatsapp/business_phone_number/account/transport.rb +30 -0
- data/lib/ruby/whatsapp/business_phone_number/account/update.rb +76 -0
- data/lib/ruby/whatsapp/business_phone_number/account.rb +23 -0
- data/lib/ruby/whatsapp/business_phone_number/contact_book/delete.rb +87 -0
- data/lib/ruby/whatsapp/business_phone_number/contact_book/deletion.rb +50 -0
- data/lib/ruby/whatsapp/business_phone_number/contact_book.rb +25 -0
- data/lib/ruby/whatsapp/business_phone_number/profile/details.rb +105 -0
- data/lib/ruby/whatsapp/business_phone_number/profile/get.rb +56 -0
- data/lib/ruby/whatsapp/business_phone_number/profile/update.rb +148 -0
- data/lib/ruby/whatsapp/business_phone_number/profile/verticals.rb +54 -0
- data/lib/ruby/whatsapp/business_phone_number/profile.rb +22 -0
- data/lib/ruby/whatsapp/business_phone_number/response.rb +3 -3
- data/lib/ruby/whatsapp/business_phone_number/transport.rb +12 -12
- data/lib/ruby/whatsapp/business_phone_number/username/claim.rb +180 -0
- data/lib/ruby/whatsapp/business_phone_number/username/delete.rb +36 -0
- data/lib/ruby/whatsapp/business_phone_number/username/details.rb +66 -0
- data/lib/ruby/whatsapp/business_phone_number/username/get.rb +33 -0
- data/lib/ruby/whatsapp/business_phone_number/username/suggest.rb +37 -0
- data/lib/ruby/whatsapp/business_phone_number/username/suggestions.rb +50 -0
- data/lib/ruby/whatsapp/business_phone_number/username.rb +27 -0
- data/lib/ruby/whatsapp/business_phone_number.rb +20 -4
- data/lib/ruby/whatsapp/message_templates/button/request_contact_info.rb +66 -0
- data/lib/ruby/whatsapp/message_templates/button.rb +5 -2
- data/lib/ruby/whatsapp/message_templates/component_set.rb +28 -0
- data/lib/ruby/whatsapp/message_templates/response/paging.rb +2 -1
- data/lib/ruby/whatsapp/message_templates.rb +2 -6
- data/lib/ruby/whatsapp/messages/base.rb +47 -5
- data/lib/ruby/whatsapp/messages/interactive/request_contact_info.rb +59 -0
- data/lib/ruby/whatsapp/messages/interactive.rb +22 -4
- data/lib/ruby/whatsapp/messages/response/contacts.rb +22 -5
- data/lib/ruby/whatsapp/path_building.rb +33 -0
- data/lib/ruby/whatsapp/schedule/create.rb +101 -0
- data/lib/ruby/whatsapp/schedule/list.rb +50 -0
- data/lib/ruby/whatsapp/schedule/response/collection.rb +61 -0
- data/lib/ruby/whatsapp/schedule/response/created.rb +30 -0
- data/lib/ruby/whatsapp/schedule/response/node.rb +85 -0
- data/lib/ruby/whatsapp/schedule/response.rb +18 -0
- data/lib/ruby/whatsapp/schedule/statuses.rb +19 -0
- data/lib/ruby/whatsapp/schedule/transport.rb +24 -0
- data/lib/ruby/whatsapp/schedule.rb +15 -0
- data/lib/ruby/whatsapp/subscribed_app/transport.rb +6 -5
- data/lib/ruby/whatsapp/version.rb +1 -1
- data/lib/ruby/whatsapp/webhook/business_username_updates.rb +51 -0
- data/lib/ruby/whatsapp/webhook/change.rb +3 -1
- data/lib/ruby/whatsapp/webhook/contact.rb +26 -3
- data/lib/ruby/whatsapp/webhook/history/chunk.rb +50 -0
- data/lib/ruby/whatsapp/webhook/history/thread.rb +64 -0
- data/lib/ruby/whatsapp/webhook/history.rb +73 -34
- data/lib/ruby/whatsapp/webhook/message/base.rb +27 -2
- data/lib/ruby/whatsapp/webhook/message/contacts/contact.rb +15 -2
- data/lib/ruby/whatsapp/webhook/message/context.rb +23 -2
- data/lib/ruby/whatsapp/webhook/message/edit.rb +50 -0
- data/lib/ruby/whatsapp/webhook/message/revoke.rb +36 -0
- data/lib/ruby/whatsapp/webhook/message/system.rb +19 -2
- data/lib/ruby/whatsapp/webhook/message/unknown.rb +2 -2
- data/lib/ruby/whatsapp/webhook/message.rb +3 -0
- data/lib/ruby/whatsapp/webhook/messages.rb +4 -3
- data/lib/ruby/whatsapp/webhook/smb_app_state_sync/contact.rb +76 -0
- data/lib/ruby/whatsapp/webhook/smb_app_state_sync/state_sync.rb +18 -4
- data/lib/ruby/whatsapp/webhook/smb_app_state_sync.rb +33 -9
- data/lib/ruby/whatsapp/webhook/smb_message_echoes/echo.rb +60 -0
- data/lib/ruby/whatsapp/webhook/smb_message_echoes.rb +26 -11
- data/lib/ruby/whatsapp/webhook/status.rb +23 -2
- data/lib/ruby/whatsapp/webhook/user_id_update/entry.rb +56 -0
- data/lib/ruby/whatsapp/webhook/user_id_update/id_change.rb +35 -0
- data/lib/ruby/whatsapp/webhook/user_id_update.rb +61 -0
- data/lib/ruby/whatsapp/webhook/user_preferences/entry.rb +76 -0
- data/lib/ruby/whatsapp/webhook/user_preferences.rb +44 -23
- metadata +100 -1
data/docs/README.md
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# ruby-whatsapp documentation
|
|
2
|
+
|
|
3
|
+
Full reference for [ruby-whatsapp](https://github.com/saleszera/ruby-whatsapp). The
|
|
4
|
+
[project README](../README.md) is the short version.
|
|
5
|
+
|
|
6
|
+
## Start here
|
|
7
|
+
|
|
8
|
+
| Page | Covers |
|
|
9
|
+
| --- | --- |
|
|
10
|
+
| [configuration.md](configuration.md) | Credentials, the client, connection reuse, instrumentation, which ID addresses what |
|
|
11
|
+
| [errors.md](errors.md) | The full exception hierarchy, validation vs API failures, retry strategy |
|
|
12
|
+
|
|
13
|
+
## Outbound — talking to customers
|
|
14
|
+
|
|
15
|
+
| Page | Covers |
|
|
16
|
+
| --- | --- |
|
|
17
|
+
| [messages/](messages/README.md) | Sending every message kind, the shared envelope, responses |
|
|
18
|
+
| [media/](media/README.md) | Upload, download, delete, and the token-safety allowlist |
|
|
19
|
+
| [message_templates/](message_templates/README.md) | Creating and managing the templates you send |
|
|
20
|
+
| [schedule/](schedule/README.md) | Scheduling an approved template to go out to an audience later |
|
|
21
|
+
|
|
22
|
+
### Message kinds
|
|
23
|
+
|
|
24
|
+
[text](messages/text.md) ·
|
|
25
|
+
[image](messages/image.md) ·
|
|
26
|
+
[video](messages/video.md) ·
|
|
27
|
+
[audio](messages/audio.md) ·
|
|
28
|
+
[document](messages/document.md) ·
|
|
29
|
+
[sticker](messages/sticker.md) ·
|
|
30
|
+
[reaction](messages/reaction.md) ·
|
|
31
|
+
[location](messages/location.md) ·
|
|
32
|
+
[contacts](messages/contacts.md) ·
|
|
33
|
+
[address](messages/address.md) ·
|
|
34
|
+
[location request](messages/location_request.md) ·
|
|
35
|
+
[template](messages/template.md) ·
|
|
36
|
+
[interactive](messages/interactive.md) ·
|
|
37
|
+
[mark as read](messages/mark_message_as_read.md)
|
|
38
|
+
|
|
39
|
+
### Template kinds
|
|
40
|
+
|
|
41
|
+
[standard](message_templates/standard.md) ·
|
|
42
|
+
[authentication](message_templates/authentication.md) ·
|
|
43
|
+
[carousel](message_templates/carousel.md) ·
|
|
44
|
+
[limited-time offer](message_templates/limited_time_offer.md) ·
|
|
45
|
+
[library](message_templates/library.md) ·
|
|
46
|
+
[components reference](message_templates/components.md) ·
|
|
47
|
+
[responses](message_templates/responses.md)
|
|
48
|
+
|
|
49
|
+
## Inbound — hearing back
|
|
50
|
+
|
|
51
|
+
| Page | Covers |
|
|
52
|
+
| --- | --- |
|
|
53
|
+
| [webhooks/](webhooks/README.md) | Install, verification, signature, dispatch, all 21 notification fields |
|
|
54
|
+
| [webhooks/messages.md](webhooks/messages.md) | Inbound messages and delivery statuses — the one you'll actually use |
|
|
55
|
+
|
|
56
|
+
## Account administration
|
|
57
|
+
|
|
58
|
+
| Page | Covers |
|
|
59
|
+
| --- | --- |
|
|
60
|
+
| [subscribed_app/](subscribed_app/README.md) | Turning webhook delivery on and off for a WABA |
|
|
61
|
+
| [business_phone_number/](business_phone_number/README.md) | Onboarding a phone number: request code → verify → register |
|
|
62
|
+
| [business_phone_number/account.md](business_phone_number/account.md) | Reading and updating the business account itself |
|
|
63
|
+
| [business_phone_number/profile.md](business_phone_number/profile.md) | Reading and updating the business profile a user sees |
|
|
64
|
+
| [business_phone_number/username.md](business_phone_number/username.md) | Claiming and managing the searchable business username |
|
|
65
|
+
| [business_phone_number/contact_book.md](business_phone_number/contact_book.md) | Removing a stored user contact from Meta's contact book |
|
|
66
|
+
|
|
67
|
+
## Reading order
|
|
68
|
+
|
|
69
|
+
New to the Cloud API? Follow the setup path in order:
|
|
70
|
+
|
|
71
|
+
1. [configuration.md](configuration.md) — get credentials in place
|
|
72
|
+
2. [business_phone_number/](business_phone_number/README.md) — register the number
|
|
73
|
+
3. [messages/text.md](messages/text.md) — send something
|
|
74
|
+
4. [subscribed_app/](subscribed_app/README.md) — turn on webhook delivery
|
|
75
|
+
5. [webhooks/](webhooks/README.md) — receive the reply
|
|
76
|
+
6. [message_templates/](message_templates/README.md) — message outside the 24-hour window
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
# Registering Business Phone Numbers
|
|
2
|
+
|
|
3
|
+
A business phone number is unusable with Cloud API until it is **registered**.
|
|
4
|
+
Registration is the prerequisite that makes sending, media, and templates work for that
|
|
5
|
+
number at all — not an optional extra.
|
|
6
|
+
|
|
7
|
+
`Whatsapp::BusinessPhoneNumber` wraps all four endpoints in that path:
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
RequestCode -> VerifyCode -> Register (onboarding)
|
|
11
|
+
(send OTP) (confirm it) (activate on Cloud API)
|
|
12
|
+
|
|
13
|
+
Deregister (the reverse switch)
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Addresses your **phone number** (`phone_id`, not `waba_id`), like
|
|
17
|
+
[messages](../messages/README.md) and [media](../media/README.md), and needs both the
|
|
18
|
+
`whatsapp_business_messaging` and `whatsapp_business_management` permissions.
|
|
19
|
+
|
|
20
|
+
> **Looking for the account itself?** [`Account::Get` / `Account::Update`](account.md)
|
|
21
|
+
> read and update the WhatsApp Business Account a number belongs to — its name,
|
|
22
|
+
> timezone, review and verification status. Those are the one `waba_id`-scoped corner
|
|
23
|
+
> of this module.
|
|
24
|
+
|
|
25
|
+
> **Looking for the profile a user sees?** [`Profile::Get` / `Profile::Update`](profile.md)
|
|
26
|
+
> read and write the about line, description, address, email, websites, industry vertical,
|
|
27
|
+
> and picture. Those address your phone number (`phone_id`), just like the four actions
|
|
28
|
+
> below.
|
|
29
|
+
|
|
30
|
+
> **Looking for the username?** [`Username::Get` / `Claim` / `Delete` / `Suggest`](username.md)
|
|
31
|
+
> read and set the searchable `@name` a WhatsApp user can find the number by, and
|
|
32
|
+
> [`ContactBook::Delete`](contact_book.md) forgets a stored user contact. Both address
|
|
33
|
+
> `phone_id`, like the four actions below.
|
|
34
|
+
|
|
35
|
+
> **Unrelated to [`SubscribedApp`](../subscribed_app/README.md).** That turns *webhook
|
|
36
|
+
> delivery* on and off for a whole WhatsApp Business Account. This is per phone number
|
|
37
|
+
> and has nothing to do with notifications.
|
|
38
|
+
|
|
39
|
+
## The whole flow
|
|
40
|
+
|
|
41
|
+
```ruby
|
|
42
|
+
Whatsapp.configure do |config|
|
|
43
|
+
config.api_key = ENV.fetch("WHATSAPP_API_KEY")
|
|
44
|
+
config.phone_id = ENV.fetch("WHATSAPP_PHONE_ID")
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
Whatsapp::BusinessPhoneNumber::RequestCode.call(code_method: "SMS", language: "en_US")
|
|
48
|
+
Whatsapp::BusinessPhoneNumber::VerifyCode.call(code: "123456")
|
|
49
|
+
Whatsapp::BusinessPhoneNumber::Register.call(pin: "212834")
|
|
50
|
+
Whatsapp::BusinessPhoneNumber::Deregister.call
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
| Method | Request | Returns |
|
|
54
|
+
| --- | --- | --- |
|
|
55
|
+
| `RequestCode.call(code_method:, language:)` | `POST /{phone_id}/request_code` | `Response` |
|
|
56
|
+
| `VerifyCode.call(code:)` | `POST /{phone_id}/verify_code` | `Response` (may carry `id`) |
|
|
57
|
+
| `Register.call(pin:, data_localization_region:)` | `POST /{phone_id}/register` | `Response` |
|
|
58
|
+
| `Deregister.call` | `POST /{phone_id}/deregister` | `Response` |
|
|
59
|
+
| [`Account::Get.call(fields:)`](account.md) | `GET /{waba_id}` | `Account::Details` |
|
|
60
|
+
| [`Account::Update.call(name:, timezone_id:)`](account.md) | `POST /{waba_id}` | `Response` |
|
|
61
|
+
| [`Profile::Get.call(fields:)`](profile.md) | `GET /{phone_id}/whatsapp_business_profile` | `Profile::Details` |
|
|
62
|
+
| [`Profile::Update.call(**fields)`](profile.md) | `POST /{phone_id}/whatsapp_business_profile` | `Response` |
|
|
63
|
+
| [`Username::Get.call`](username.md) | `GET /{phone_id}/username` | `Username::Details` |
|
|
64
|
+
| [`Username::Claim.call(username:, transfer_action:)`](username.md) | `POST /{phone_id}/username` | `Username::Details` |
|
|
65
|
+
| [`Username::Delete.call`](username.md) | `DELETE /{phone_id}/username` | `Response` |
|
|
66
|
+
| [`Username::Suggest.call`](username.md) | `GET /{phone_id}/username_suggestions` | `Username::Suggestions` |
|
|
67
|
+
| [`ContactBook::Delete.call(bsuid:)`](contact_book.md) | `DELETE /{phone_id}/contact_book` | `ContactBook::Deletion` |
|
|
68
|
+
|
|
69
|
+
Every action accepts an optional `client:` and raises
|
|
70
|
+
`Whatsapp::BusinessPhoneNumber::Error` if `phone_id` is missing or the API rejects the
|
|
71
|
+
request.
|
|
72
|
+
|
|
73
|
+
## Requesting a verification code
|
|
74
|
+
|
|
75
|
+
Sends a one-time code to the number, by SMS or automated voice call.
|
|
76
|
+
|
|
77
|
+
```ruby
|
|
78
|
+
Whatsapp::BusinessPhoneNumber::RequestCode.call(code_method: "SMS", language: "en_US").success
|
|
79
|
+
# => true
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
| Field | Required | Rules |
|
|
83
|
+
| --- | --- | --- |
|
|
84
|
+
| `code_method` | yes | `"SMS"` or `"VOICE"` — either casing accepted, emitted uppercase |
|
|
85
|
+
| `language` | yes | The locale for the message, e.g. `"en_US"`. Presence only |
|
|
86
|
+
|
|
87
|
+
> `language` is deliberately **not** checked against `Utils::LanguageCodes`. That list
|
|
88
|
+
> is the set of locales Meta *approves templates* in; this field is closer to a
|
|
89
|
+
> delivery preference, and Meta documents it as a plain string with no enum.
|
|
90
|
+
|
|
91
|
+
```ruby
|
|
92
|
+
Whatsapp::BusinessPhoneNumber::RequestCode.call(code_method: "CARRIER_PIGEON", language: "en_US")
|
|
93
|
+
# => ActiveModel::ValidationError: Code method is not included in the list
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Both fields are always sent — neither is compacted away:
|
|
97
|
+
|
|
98
|
+
```ruby
|
|
99
|
+
Whatsapp::BusinessPhoneNumber::RequestCode.new(code_method: "sms", language: "en_US").serialize
|
|
100
|
+
# => { code_method: "SMS", language: "en_US" }
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Verifying the code
|
|
104
|
+
|
|
105
|
+
```ruby
|
|
106
|
+
result = Whatsapp::BusinessPhoneNumber::VerifyCode.call(code: "123456")
|
|
107
|
+
|
|
108
|
+
result.success # => true
|
|
109
|
+
result.id # => "106540352242922" — the phone number ID, when Meta returns one
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
`code` is checked for presence only — Meta documents no format, so a wrong-but-present
|
|
113
|
+
code is rejected server-side.
|
|
114
|
+
|
|
115
|
+
The OTP is redacted from `#inspect`:
|
|
116
|
+
|
|
117
|
+
```ruby
|
|
118
|
+
Whatsapp::BusinessPhoneNumber::VerifyCode.new(code: "123456").inspect
|
|
119
|
+
# => "#<Whatsapp::BusinessPhoneNumber::VerifyCode code=[REDACTED]>"
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Registering
|
|
123
|
+
|
|
124
|
+
The step that actually activates the number on Cloud API.
|
|
125
|
+
|
|
126
|
+
```ruby
|
|
127
|
+
Whatsapp::BusinessPhoneNumber::Register.call(pin: "212834").success
|
|
128
|
+
# => true
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
| Field | Required | Rules |
|
|
132
|
+
| --- | --- | --- |
|
|
133
|
+
| `pin` | yes | Exactly **6 digits** — the existing two-step verification PIN, or the one to set |
|
|
134
|
+
| `data_localization_region` | no | One of 14 region codes |
|
|
135
|
+
|
|
136
|
+
```ruby
|
|
137
|
+
Whatsapp::BusinessPhoneNumber::Register.call(pin: "1234")
|
|
138
|
+
# => ActiveModel::ValidationError: Pin must be exactly 6 digits
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Data localization
|
|
142
|
+
|
|
143
|
+
Passing a region enables local storage — message data at rest stays in that region:
|
|
144
|
+
|
|
145
|
+
```ruby
|
|
146
|
+
Whatsapp::BusinessPhoneNumber::Register.call(pin: "212834", data_localization_region: "CH")
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
| Region | Codes |
|
|
150
|
+
| --- | --- |
|
|
151
|
+
| APAC | `AU` `ID` `IN` `JP` `SG` `KR` |
|
|
152
|
+
| Europe | `DE` `CH` `GB` |
|
|
153
|
+
| LATAM | `BR` |
|
|
154
|
+
| MEA | `BH` `ZA` `AE` |
|
|
155
|
+
| NORAM | `CA` |
|
|
156
|
+
|
|
157
|
+
Omitted when not given:
|
|
158
|
+
|
|
159
|
+
```ruby
|
|
160
|
+
Whatsapp::BusinessPhoneNumber::Register.new(pin: "212834").serialize
|
|
161
|
+
# => { messaging_product: "whatsapp", pin: "212834" }
|
|
162
|
+
|
|
163
|
+
Whatsapp::BusinessPhoneNumber::Register.new(pin: "212834", data_localization_region: "ch").serialize
|
|
164
|
+
# => { messaging_product: "whatsapp", pin: "212834", data_localization_region: "CH" }
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
The PIN is redacted from `#inspect`, and no validation error message ever echoes it:
|
|
168
|
+
|
|
169
|
+
```ruby
|
|
170
|
+
Whatsapp::BusinessPhoneNumber::Register.new(pin: "212834", data_localization_region: "CH").inspect
|
|
171
|
+
# => "#<Whatsapp::BusinessPhoneNumber::Register pin=[REDACTED] data_localization_region=\"CH\">"
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## Deregistering
|
|
175
|
+
|
|
176
|
+
```ruby
|
|
177
|
+
Whatsapp::BusinessPhoneNumber::Deregister.call.success # => true
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Deregistering makes the number unusable with Cloud API and disables local storage. It
|
|
181
|
+
does **not** delete the number or its message history. A number in use with both Cloud
|
|
182
|
+
API and the WhatsApp Business app cannot be deregistered.
|
|
183
|
+
|
|
184
|
+
`Deregister` takes no arguments and has nothing to validate — its payload is `{}`.
|
|
185
|
+
|
|
186
|
+
## Targeting a different number
|
|
187
|
+
|
|
188
|
+
Inject a client rather than mutating global configuration:
|
|
189
|
+
|
|
190
|
+
```ruby
|
|
191
|
+
Whatsapp::BusinessPhoneNumber::Register.call(
|
|
192
|
+
pin: "212834",
|
|
193
|
+
client: Whatsapp::Client.new(phone_id: "OTHER_PHONE_ID")
|
|
194
|
+
)
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
## Rate limits
|
|
198
|
+
|
|
199
|
+
> **`Register` / `Deregister`:** 10 requests per business number in a **72-hour moving
|
|
200
|
+
> window**. Exceeding it returns error `133016` and blocks the operation for the next
|
|
201
|
+
> 72 hours.
|
|
202
|
+
>
|
|
203
|
+
> **`RequestCode` / `VerifyCode`:** "standard Graph API rate limits", plus possible
|
|
204
|
+
> additional throttling — Meta publishes no number.
|
|
205
|
+
|
|
206
|
+
This is server-side state the gem cannot observe, so there is no client-side tracking.
|
|
207
|
+
It is also why every action validates locally first: a rejected attempt still counts
|
|
208
|
+
against the limit.
|
|
209
|
+
|
|
210
|
+
## Errors
|
|
211
|
+
|
|
212
|
+
```ruby
|
|
213
|
+
Whatsapp::BusinessPhoneNumber::Register.call(pin: "212834")
|
|
214
|
+
# => Whatsapp::BusinessPhoneNumber::Error: phone_id is required for the register edge;
|
|
215
|
+
# set it via Whatsapp.configure or Client.new(phone_id:)
|
|
216
|
+
|
|
217
|
+
# non-2xx response
|
|
218
|
+
# => Whatsapp::BusinessPhoneNumber::Error: Failed to register business phone number:
|
|
219
|
+
# 400 Bad Request - {"error":{"message":"..."}}
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
Local validation failures raise `ActiveModel::ValidationError` before any request. See
|
|
223
|
+
[../errors.md](../errors.md).
|
|
224
|
+
|
|
225
|
+
## Not wrapped
|
|
226
|
+
|
|
227
|
+
- **Client-side rate-limit tracking** — server-side state the gem cannot observe.
|
|
228
|
+
- **Two-step verification PIN management.** `Register` *consumes* an existing PIN;
|
|
229
|
+
setting or changing one is a different endpoint.
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
**See also:** [Reading and updating the business account](account.md) ·
|
|
234
|
+
[Reading and updating the business profile](profile.md) ·
|
|
235
|
+
[Business usernames](username.md) · [Contact book](contact_book.md)
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
239
|
+
**Meta docs:**
|
|
240
|
+
[registration](https://developers.facebook.com/documentation/business-messaging/whatsapp/business-phone-numbers/registration)
|
|
241
|
+
· [request code](https://developers.facebook.com/documentation/business-messaging/whatsapp/reference/whatsapp-business-phone-number/phone-number-verification-request-code-api)
|
|
242
|
+
· [verify code](https://developers.facebook.com/documentation/business-messaging/whatsapp/reference/whatsapp-business-phone-number/verify-code-api)
|
|
243
|
+
· [deregister](https://developers.facebook.com/documentation/business-messaging/whatsapp/reference/whatsapp-business-phone-number/phone-number-deregister-api)
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# Reading and Updating the Business Account
|
|
2
|
+
|
|
3
|
+
Every business phone number belongs to a **WhatsApp Business Account** (WABA). The rest
|
|
4
|
+
of this module drives a *number's* onboarding; `Whatsapp::BusinessPhoneNumber::Account`
|
|
5
|
+
reads and writes the *account* that number sits inside — whether Meta has approved it,
|
|
6
|
+
whether the business behind it is verified, who owns it, and what it is called.
|
|
7
|
+
|
|
8
|
+
> **The one `waba_id` corner of this module.**
|
|
9
|
+
> [`RequestCode`/`VerifyCode`/`Register`/`Deregister`](README.md) address your phone
|
|
10
|
+
> number (`phone_id`). `Account` addresses the account (`waba_id`), like
|
|
11
|
+
> [template management](../message_templates/README.md) and
|
|
12
|
+
> [subscribed apps](../subscribed_app/README.md), and needs the
|
|
13
|
+
> `whatsapp_business_management` permission.
|
|
14
|
+
|
|
15
|
+
```ruby
|
|
16
|
+
Whatsapp.configure do |config|
|
|
17
|
+
config.api_key = ENV.fetch("WHATSAPP_API_KEY")
|
|
18
|
+
config.waba_id = ENV.fetch("WHATSAPP_WABA_ID")
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
Whatsapp::BusinessPhoneNumber::Account::Get.call.name # => "Acme Corp"
|
|
22
|
+
Whatsapp::BusinessPhoneNumber::Account::Update.call(name: "Acme") # => #<Response success=true>
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
| Method | Request | Returns |
|
|
26
|
+
| --- | --- | --- |
|
|
27
|
+
| `Get.call(client:, fields:)` | `GET /{waba_id}` | `Account::Details` |
|
|
28
|
+
| `Update.call(client:, name:, timezone_id:)` | `POST /{waba_id}` | `BusinessPhoneNumber::Response` |
|
|
29
|
+
|
|
30
|
+
Two independent actions, so two classes — the same shape as
|
|
31
|
+
[subscribed apps](../subscribed_app/README.md), not the single-object CRUD of
|
|
32
|
+
[templates](../message_templates/README.md).
|
|
33
|
+
|
|
34
|
+
## Reading account details
|
|
35
|
+
|
|
36
|
+
```ruby
|
|
37
|
+
details = Whatsapp::BusinessPhoneNumber::Account::Get.call
|
|
38
|
+
|
|
39
|
+
details.id # => "102290129340398"
|
|
40
|
+
details.name # => "Acme Corp"
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
By default Meta returns only `id` and `name`. Ask for more with `fields:` — an Array or
|
|
44
|
+
a String, comma-joined for you:
|
|
45
|
+
|
|
46
|
+
```ruby
|
|
47
|
+
Whatsapp::BusinessPhoneNumber::Account::Get.call(fields: %w[id name country])
|
|
48
|
+
Whatsapp::BusinessPhoneNumber::Account::Get.call(fields: "id,name,country")
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
`Fields::ALL` requests everything Meta publishes, without hand-typing the list:
|
|
52
|
+
|
|
53
|
+
```ruby
|
|
54
|
+
details = Whatsapp::BusinessPhoneNumber::Account::Get.call(
|
|
55
|
+
fields: Whatsapp::BusinessPhoneNumber::Account::Get::Fields::ALL
|
|
56
|
+
)
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
| Field | Description |
|
|
60
|
+
| --- | --- |
|
|
61
|
+
| `id` | The account's unique identifier |
|
|
62
|
+
| `name` | Human-readable account name |
|
|
63
|
+
| `timezone_id` | Timezone identifier |
|
|
64
|
+
| `message_template_namespace` | Namespace the account's templates live in |
|
|
65
|
+
| `account_review_status` | One of `Details::ReviewStatuses::ALL` |
|
|
66
|
+
| `business_verification_status` | One of `Details::VerificationStatuses::ALL` |
|
|
67
|
+
| `country` | Country code |
|
|
68
|
+
| `ownership_type` | One of `Details::OwnershipTypes::ALL` |
|
|
69
|
+
| `primary_business_location` | Primary business location |
|
|
70
|
+
|
|
71
|
+
> `fields` is **not** validated against `Fields::ALL`. Those constants are for
|
|
72
|
+
> convenience, not enforcement — a field Meta adds later works without a gem release.
|
|
73
|
+
> Only what Meta documents as a *client-side* rule is checked locally.
|
|
74
|
+
|
|
75
|
+
### Status values
|
|
76
|
+
|
|
77
|
+
Statuses come back as plain strings, with frozen constants to compare against and three
|
|
78
|
+
predicates for the checks you actually make:
|
|
79
|
+
|
|
80
|
+
```ruby
|
|
81
|
+
details.approved? # => true — account_review_status == "APPROVED"
|
|
82
|
+
details.verified? # => true — business_verification_status == "VERIFIED"
|
|
83
|
+
details.self_owned? # => true — ownership_type == "SELF"
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
| Constant | Values |
|
|
87
|
+
| --- | --- |
|
|
88
|
+
| `Details::ReviewStatuses::ALL` | `APPROVED` `DEFERRED` `PENDING` `REJECTED` |
|
|
89
|
+
| `Details::VerificationStatuses::ALL` | `EXPIRED` `FAILED` `INELIGIBLE` `NOT_VERIFIED` `PENDING` `PENDING_NEED_MORE_INFO` `PENDING_SUBMISSION` `REJECTED` `REVOKED` `VERIFIED` |
|
|
90
|
+
| `Details::OwnershipTypes::ALL` | `CLIENT_OWNED` `ON_BEHALF_OF` `SELF` |
|
|
91
|
+
|
|
92
|
+
> Read-side values are never validated on the way in. A status Meta adds later still
|
|
93
|
+
> round-trips untouched, so `details.account_review_status` is always exactly what the
|
|
94
|
+
> API sent — same reasoning as
|
|
95
|
+
> [`MessageTemplates::Response::Node#components`](../message_templates/responses.md).
|
|
96
|
+
|
|
97
|
+
Every field is optional in the response, so anything you did not request is `nil`
|
|
98
|
+
rather than an error.
|
|
99
|
+
|
|
100
|
+
## Updating the account
|
|
101
|
+
|
|
102
|
+
```ruby
|
|
103
|
+
Whatsapp::BusinessPhoneNumber::Account::Update.call(name: "Acme Corporation").success
|
|
104
|
+
# => true
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
| Field | Required | Rules |
|
|
108
|
+
| --- | --- | --- |
|
|
109
|
+
| `name` | no | Non-empty when given |
|
|
110
|
+
| `timezone_id` | no | Non-empty when given. Meta publishes no enum — presence only |
|
|
111
|
+
|
|
112
|
+
Both fields are optional, but **at least one must be present** — otherwise the request
|
|
113
|
+
would spend a call to change nothing:
|
|
114
|
+
|
|
115
|
+
```ruby
|
|
116
|
+
Whatsapp::BusinessPhoneNumber::Account::Update.call
|
|
117
|
+
# => ActiveModel::ValidationError: at least one of name or timezone_id must be provided
|
|
118
|
+
|
|
119
|
+
Whatsapp::BusinessPhoneNumber::Account::Update.call(name: "")
|
|
120
|
+
# => ActiveModel::ValidationError: Name can't be blank
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
An omitted field is compacted out of the body rather than sent as `null`, which Meta
|
|
124
|
+
would read as an instruction to blank it:
|
|
125
|
+
|
|
126
|
+
```ruby
|
|
127
|
+
Whatsapp::BusinessPhoneNumber::Account::Update.new(name: "Acme Corp").serialize
|
|
128
|
+
# => { name: "Acme Corp" }
|
|
129
|
+
|
|
130
|
+
Whatsapp::BusinessPhoneNumber::Account::Update.new(name: "Acme Corp", timezone_id: "1").serialize
|
|
131
|
+
# => { name: "Acme Corp", timezone_id: "1" }
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
`Update` returns the same `Whatsapp::BusinessPhoneNumber::Response` as the four
|
|
135
|
+
onboarding actions — the endpoint answers with a bare `{ "success": true }`, so there is
|
|
136
|
+
no separate response class for it.
|
|
137
|
+
|
|
138
|
+
## Errors
|
|
139
|
+
|
|
140
|
+
```ruby
|
|
141
|
+
Whatsapp::BusinessPhoneNumber::Account::Get.call
|
|
142
|
+
# => Whatsapp::BusinessPhoneNumber::Error: waba_id is required for business account
|
|
143
|
+
# details; set it via Whatsapp.configure or Client.new(waba_id:)
|
|
144
|
+
|
|
145
|
+
# non-2xx response
|
|
146
|
+
# => Whatsapp::BusinessPhoneNumber::Error: Failed to get business account details:
|
|
147
|
+
# 404 Not Found - {"error":{"message":"WhatsApp Business Account not found",...}}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Both actions raise `Whatsapp::BusinessPhoneNumber::Error` — the same class the
|
|
151
|
+
onboarding actions use, so one `rescue` covers the whole module. Local validation
|
|
152
|
+
failures raise `ActiveModel::ValidationError` before any request. See
|
|
153
|
+
[../errors.md](../errors.md).
|
|
154
|
+
|
|
155
|
+
## Targeting a different account
|
|
156
|
+
|
|
157
|
+
```ruby
|
|
158
|
+
Whatsapp::BusinessPhoneNumber::Account::Get.call(
|
|
159
|
+
client: Whatsapp::Client.new(waba_id: "OTHER_WABA_ID")
|
|
160
|
+
)
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
**See also:** [Reading and updating the business profile](profile.md) ·
|
|
166
|
+
[Registering business phone numbers](README.md)
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
**Meta docs:** <https://developers.facebook.com/documentation/business-messaging/whatsapp/reference/whatsapp-business-account/whatsapp-business-account-api>
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# Contact book
|
|
2
|
+
|
|
3
|
+
Meta keeps a **contact book** for your business portfolio: the phone numbers and BSUIDs
|
|
4
|
+
of users you have interacted with. Its purpose is thread continuity — once a user adopts
|
|
5
|
+
a username, Meta will keep putting their phone number in your webhooks if they are in
|
|
6
|
+
your contact book, instead of leaving you with only their
|
|
7
|
+
[BSUID](../webhooks/messages.md#business-scoped-user-ids-bsuid).
|
|
8
|
+
|
|
9
|
+
The store itself is hosted by Meta and needs no integration work; entries appear on their
|
|
10
|
+
own as you exchange messages or calls, and when a user taps a `REQUEST_CONTACT_INFO`
|
|
11
|
+
button (which arrives as a [`contacts` message](../webhooks/messages.md#contacts) with
|
|
12
|
+
`origin == "contact_request"`). This page covers the one operation Meta exposes on it:
|
|
13
|
+
removing an entry.
|
|
14
|
+
|
|
15
|
+
| Method | Request | Returns |
|
|
16
|
+
| --- | --- | --- |
|
|
17
|
+
| `ContactBook::Delete.call(bsuid:)` | `DELETE /{phone_id}/contact_book` | `ContactBook::Deletion` |
|
|
18
|
+
|
|
19
|
+
## Deleting an entry
|
|
20
|
+
|
|
21
|
+
```ruby
|
|
22
|
+
result = Whatsapp::BusinessPhoneNumber::ContactBook::Delete.call(bsuid: "US.13491208655302741918")
|
|
23
|
+
|
|
24
|
+
result.success # => true — the request was processed
|
|
25
|
+
result.deleted # => true — an entry existed and is gone
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
The two booleans are independent, which is why this returns its own class rather than the
|
|
29
|
+
shared `Response`:
|
|
30
|
+
|
|
31
|
+
| `success` | `deleted` | Meaning |
|
|
32
|
+
| --- | --- | --- |
|
|
33
|
+
| `true` | `true` | An entry existed and was removed |
|
|
34
|
+
| `true` | `false` | The request was fine; there was nothing stored for that BSUID |
|
|
35
|
+
| `false` | — | The request was not processed |
|
|
36
|
+
|
|
37
|
+
So `deleted == false` is **not** a failure. Check `success` for that, or rescue
|
|
38
|
+
`Whatsapp::BusinessPhoneNumber::Error`.
|
|
39
|
+
|
|
40
|
+
## What it affects
|
|
41
|
+
|
|
42
|
+
> **The edge is on one phone number; the effect is portfolio-wide.** Contact books are
|
|
43
|
+
> scoped to the business portfolio, so deleting an entry removes that user's phone number
|
|
44
|
+
> from webhooks for **every** business phone number in the portfolio — not just the one
|
|
45
|
+
> you addressed.
|
|
46
|
+
|
|
47
|
+
And the consequence to expect: after a deletion, `wa_id`/`from` may start being omitted
|
|
48
|
+
from your webhooks for that user, leaving their BSUID as the only identifier. That is the
|
|
49
|
+
feature working, not a bug — but code that keys off `wa_id` will notice.
|
|
50
|
+
|
|
51
|
+
The phone number can come back, without a new deletion, if the user messages or calls you
|
|
52
|
+
again (or you them) within the 30-day window Meta caches, since that recreates the entry.
|
|
53
|
+
|
|
54
|
+
## Rules
|
|
55
|
+
|
|
56
|
+
- **Standard BSUIDs only.** Parent BSUIDs are not supported. Since this release hands you
|
|
57
|
+
`user_id` and `parent_user_id` side by side on half a dozen webhook classes, the mix-up
|
|
58
|
+
is easy, so it is caught locally:
|
|
59
|
+
|
|
60
|
+
```ruby
|
|
61
|
+
Whatsapp::BusinessPhoneNumber::ContactBook::Delete.call(bsuid: "US.ENT.11815799212886844830")
|
|
62
|
+
# => ActiveModel::ValidationError: Bsuid looks like a parent BSUID;
|
|
63
|
+
# the contact book accepts standard BSUIDs only
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Detected by segment count rather than by matching `ENT`, which would bet that `ENT` is
|
|
67
|
+
the only marker Meta will ever use.
|
|
68
|
+
|
|
69
|
+
- **Same portfolio.** The BSUID must belong to the same portfolio as the phone number.
|
|
70
|
+
That is account state the gem cannot see, so it is left to the API — a mismatch comes
|
|
71
|
+
back as `Whatsapp::BusinessPhoneNumber::Error`.
|
|
72
|
+
|
|
73
|
+
## Disabling the feature entirely
|
|
74
|
+
|
|
75
|
+
The contact book can be turned off in **Meta Business Suite → Business settings →
|
|
76
|
+
Business info**. Disabling it stops all storage and deletes everything already stored;
|
|
77
|
+
re-enabling starts fresh, with no way to restore the old contents. There is no API for
|
|
78
|
+
this, so it is not wrapped.
|
|
79
|
+
|
|
80
|
+
## Not wrapped
|
|
81
|
+
|
|
82
|
+
Meta documents only the delete on this edge. A future read or write belongs in this same
|
|
83
|
+
namespace.
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
**See also:** [Business usernames](username.md) ·
|
|
88
|
+
[BSUIDs in webhooks](../webhooks/messages.md#business-scoped-user-ids-bsuid)
|
|
89
|
+
|
|
90
|
+
**Meta docs:** https://developers.facebook.com/documentation/business-messaging/whatsapp/business-scoped-user-ids
|