ruby-whatsapp 0.4.0 → 0.4.2
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 +9 -0
- data/README.md +276 -634
- data/docs/README.md +73 -0
- data/docs/business_phone_number/README.md +232 -0
- data/docs/business_phone_number/account.md +170 -0
- data/docs/business_phone_number/profile.md +220 -0
- data/docs/configuration.md +172 -0
- data/docs/errors.md +208 -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 +179 -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 +135 -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 +281 -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 +166 -0
- data/docs/messages/text.md +55 -0
- data/docs/messages/video.md +41 -0
- data/docs/subscribed_app/README.md +139 -0
- data/docs/webhooks/README.md +209 -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/history.md +44 -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 +385 -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 +53 -0
- data/docs/webhooks/smb_message_echoes.md +54 -0
- data/docs/webhooks/template_category_update.md +52 -0
- data/docs/webhooks/user_preferences.md +48 -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/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 +11 -12
- data/lib/ruby/whatsapp/business_phone_number.rb +11 -4
- data/lib/ruby/whatsapp/message_templates.rb +2 -6
- data/lib/ruby/whatsapp/path_building.rb +33 -0
- data/lib/ruby/whatsapp/subscribed_app/transport.rb +6 -5
- data/lib/ruby/whatsapp/version.rb +1 -1
- metadata +63 -1
data/docs/README.md
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
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
|
+
|
|
21
|
+
### Message kinds
|
|
22
|
+
|
|
23
|
+
[text](messages/text.md) ·
|
|
24
|
+
[image](messages/image.md) ·
|
|
25
|
+
[video](messages/video.md) ·
|
|
26
|
+
[audio](messages/audio.md) ·
|
|
27
|
+
[document](messages/document.md) ·
|
|
28
|
+
[sticker](messages/sticker.md) ·
|
|
29
|
+
[reaction](messages/reaction.md) ·
|
|
30
|
+
[location](messages/location.md) ·
|
|
31
|
+
[contacts](messages/contacts.md) ·
|
|
32
|
+
[address](messages/address.md) ·
|
|
33
|
+
[location request](messages/location_request.md) ·
|
|
34
|
+
[template](messages/template.md) ·
|
|
35
|
+
[interactive](messages/interactive.md) ·
|
|
36
|
+
[mark as read](messages/mark_message_as_read.md)
|
|
37
|
+
|
|
38
|
+
### Template kinds
|
|
39
|
+
|
|
40
|
+
[standard](message_templates/standard.md) ·
|
|
41
|
+
[authentication](message_templates/authentication.md) ·
|
|
42
|
+
[carousel](message_templates/carousel.md) ·
|
|
43
|
+
[limited-time offer](message_templates/limited_time_offer.md) ·
|
|
44
|
+
[library](message_templates/library.md) ·
|
|
45
|
+
[components reference](message_templates/components.md) ·
|
|
46
|
+
[responses](message_templates/responses.md)
|
|
47
|
+
|
|
48
|
+
## Inbound — hearing back
|
|
49
|
+
|
|
50
|
+
| Page | Covers |
|
|
51
|
+
| --- | --- |
|
|
52
|
+
| [webhooks/](webhooks/README.md) | Install, verification, signature, dispatch, all 19 notification fields |
|
|
53
|
+
| [webhooks/messages.md](webhooks/messages.md) | Inbound messages and delivery statuses — the one you'll actually use |
|
|
54
|
+
|
|
55
|
+
## Account administration
|
|
56
|
+
|
|
57
|
+
| Page | Covers |
|
|
58
|
+
| --- | --- |
|
|
59
|
+
| [subscribed_app/](subscribed_app/README.md) | Turning webhook delivery on and off for a WABA |
|
|
60
|
+
| [business_phone_number/](business_phone_number/README.md) | Onboarding a phone number: request code → verify → register |
|
|
61
|
+
| [business_phone_number/account.md](business_phone_number/account.md) | Reading and updating the business account itself |
|
|
62
|
+
| [business_phone_number/profile.md](business_phone_number/profile.md) | Reading and updating the business profile a user sees |
|
|
63
|
+
|
|
64
|
+
## Reading order
|
|
65
|
+
|
|
66
|
+
New to the Cloud API? Follow the setup path in order:
|
|
67
|
+
|
|
68
|
+
1. [configuration.md](configuration.md) — get credentials in place
|
|
69
|
+
2. [business_phone_number/](business_phone_number/README.md) — register the number
|
|
70
|
+
3. [messages/text.md](messages/text.md) — send something
|
|
71
|
+
4. [subscribed_app/](subscribed_app/README.md) — turn on webhook delivery
|
|
72
|
+
5. [webhooks/](webhooks/README.md) — receive the reply
|
|
73
|
+
6. [message_templates/](message_templates/README.md) — message outside the 24-hour window
|
|
@@ -0,0 +1,232 @@
|
|
|
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
|
+
> **Unrelated to [`SubscribedApp`](../subscribed_app/README.md).** That turns *webhook
|
|
31
|
+
> delivery* on and off for a whole WhatsApp Business Account. This is per phone number
|
|
32
|
+
> and has nothing to do with notifications.
|
|
33
|
+
|
|
34
|
+
## The whole flow
|
|
35
|
+
|
|
36
|
+
```ruby
|
|
37
|
+
Whatsapp.configure do |config|
|
|
38
|
+
config.api_key = ENV.fetch("WHATSAPP_API_KEY")
|
|
39
|
+
config.phone_id = ENV.fetch("WHATSAPP_PHONE_ID")
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
Whatsapp::BusinessPhoneNumber::RequestCode.call(code_method: "SMS", language: "en_US")
|
|
43
|
+
Whatsapp::BusinessPhoneNumber::VerifyCode.call(code: "123456")
|
|
44
|
+
Whatsapp::BusinessPhoneNumber::Register.call(pin: "212834")
|
|
45
|
+
Whatsapp::BusinessPhoneNumber::Deregister.call
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
| Method | Request | Returns |
|
|
49
|
+
| --- | --- | --- |
|
|
50
|
+
| `RequestCode.call(code_method:, language:)` | `POST /{phone_id}/request_code` | `Response` |
|
|
51
|
+
| `VerifyCode.call(code:)` | `POST /{phone_id}/verify_code` | `Response` (may carry `id`) |
|
|
52
|
+
| `Register.call(pin:, data_localization_region:)` | `POST /{phone_id}/register` | `Response` |
|
|
53
|
+
| `Deregister.call` | `POST /{phone_id}/deregister` | `Response` |
|
|
54
|
+
| [`Account::Get.call(fields:)`](account.md) | `GET /{waba_id}` | `Account::Details` |
|
|
55
|
+
| [`Account::Update.call(name:, timezone_id:)`](account.md) | `POST /{waba_id}` | `Response` |
|
|
56
|
+
| [`Profile::Get.call(fields:)`](profile.md) | `GET /{phone_id}/whatsapp_business_profile` | `Profile::Details` |
|
|
57
|
+
| [`Profile::Update.call(**fields)`](profile.md) | `POST /{phone_id}/whatsapp_business_profile` | `Response` |
|
|
58
|
+
|
|
59
|
+
Every action accepts an optional `client:` and raises
|
|
60
|
+
`Whatsapp::BusinessPhoneNumber::Error` if `phone_id` is missing or the API rejects the
|
|
61
|
+
request.
|
|
62
|
+
|
|
63
|
+
## Requesting a verification code
|
|
64
|
+
|
|
65
|
+
Sends a one-time code to the number, by SMS or automated voice call.
|
|
66
|
+
|
|
67
|
+
```ruby
|
|
68
|
+
Whatsapp::BusinessPhoneNumber::RequestCode.call(code_method: "SMS", language: "en_US").success
|
|
69
|
+
# => true
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
| Field | Required | Rules |
|
|
73
|
+
| --- | --- | --- |
|
|
74
|
+
| `code_method` | yes | `"SMS"` or `"VOICE"` — either casing accepted, emitted uppercase |
|
|
75
|
+
| `language` | yes | The locale for the message, e.g. `"en_US"`. Presence only |
|
|
76
|
+
|
|
77
|
+
> `language` is deliberately **not** checked against `Utils::LanguageCodes`. That list
|
|
78
|
+
> is the set of locales Meta *approves templates* in; this field is closer to a
|
|
79
|
+
> delivery preference, and Meta documents it as a plain string with no enum.
|
|
80
|
+
|
|
81
|
+
```ruby
|
|
82
|
+
Whatsapp::BusinessPhoneNumber::RequestCode.call(code_method: "CARRIER_PIGEON", language: "en_US")
|
|
83
|
+
# => ActiveModel::ValidationError: Code method is not included in the list
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Both fields are always sent — neither is compacted away:
|
|
87
|
+
|
|
88
|
+
```ruby
|
|
89
|
+
Whatsapp::BusinessPhoneNumber::RequestCode.new(code_method: "sms", language: "en_US").serialize
|
|
90
|
+
# => { code_method: "SMS", language: "en_US" }
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Verifying the code
|
|
94
|
+
|
|
95
|
+
```ruby
|
|
96
|
+
result = Whatsapp::BusinessPhoneNumber::VerifyCode.call(code: "123456")
|
|
97
|
+
|
|
98
|
+
result.success # => true
|
|
99
|
+
result.id # => "106540352242922" — the phone number ID, when Meta returns one
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
`code` is checked for presence only — Meta documents no format, so a wrong-but-present
|
|
103
|
+
code is rejected server-side.
|
|
104
|
+
|
|
105
|
+
The OTP is redacted from `#inspect`:
|
|
106
|
+
|
|
107
|
+
```ruby
|
|
108
|
+
Whatsapp::BusinessPhoneNumber::VerifyCode.new(code: "123456").inspect
|
|
109
|
+
# => "#<Whatsapp::BusinessPhoneNumber::VerifyCode code=[REDACTED]>"
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Registering
|
|
113
|
+
|
|
114
|
+
The step that actually activates the number on Cloud API.
|
|
115
|
+
|
|
116
|
+
```ruby
|
|
117
|
+
Whatsapp::BusinessPhoneNumber::Register.call(pin: "212834").success
|
|
118
|
+
# => true
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
| Field | Required | Rules |
|
|
122
|
+
| --- | --- | --- |
|
|
123
|
+
| `pin` | yes | Exactly **6 digits** — the existing two-step verification PIN, or the one to set |
|
|
124
|
+
| `data_localization_region` | no | One of 14 region codes |
|
|
125
|
+
|
|
126
|
+
```ruby
|
|
127
|
+
Whatsapp::BusinessPhoneNumber::Register.call(pin: "1234")
|
|
128
|
+
# => ActiveModel::ValidationError: Pin must be exactly 6 digits
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Data localization
|
|
132
|
+
|
|
133
|
+
Passing a region enables local storage — message data at rest stays in that region:
|
|
134
|
+
|
|
135
|
+
```ruby
|
|
136
|
+
Whatsapp::BusinessPhoneNumber::Register.call(pin: "212834", data_localization_region: "CH")
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
| Region | Codes |
|
|
140
|
+
| --- | --- |
|
|
141
|
+
| APAC | `AU` `ID` `IN` `JP` `SG` `KR` |
|
|
142
|
+
| Europe | `DE` `CH` `GB` |
|
|
143
|
+
| LATAM | `BR` |
|
|
144
|
+
| MEA | `BH` `ZA` `AE` |
|
|
145
|
+
| NORAM | `CA` |
|
|
146
|
+
|
|
147
|
+
Omitted when not given:
|
|
148
|
+
|
|
149
|
+
```ruby
|
|
150
|
+
Whatsapp::BusinessPhoneNumber::Register.new(pin: "212834").serialize
|
|
151
|
+
# => { messaging_product: "whatsapp", pin: "212834" }
|
|
152
|
+
|
|
153
|
+
Whatsapp::BusinessPhoneNumber::Register.new(pin: "212834", data_localization_region: "ch").serialize
|
|
154
|
+
# => { messaging_product: "whatsapp", pin: "212834", data_localization_region: "CH" }
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
The PIN is redacted from `#inspect`, and no validation error message ever echoes it:
|
|
158
|
+
|
|
159
|
+
```ruby
|
|
160
|
+
Whatsapp::BusinessPhoneNumber::Register.new(pin: "212834", data_localization_region: "CH").inspect
|
|
161
|
+
# => "#<Whatsapp::BusinessPhoneNumber::Register pin=[REDACTED] data_localization_region=\"CH\">"
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## Deregistering
|
|
165
|
+
|
|
166
|
+
```ruby
|
|
167
|
+
Whatsapp::BusinessPhoneNumber::Deregister.call.success # => true
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Deregistering makes the number unusable with Cloud API and disables local storage. It
|
|
171
|
+
does **not** delete the number or its message history. A number in use with both Cloud
|
|
172
|
+
API and the WhatsApp Business app cannot be deregistered.
|
|
173
|
+
|
|
174
|
+
`Deregister` takes no arguments and has nothing to validate — its payload is `{}`.
|
|
175
|
+
|
|
176
|
+
## Targeting a different number
|
|
177
|
+
|
|
178
|
+
Inject a client rather than mutating global configuration:
|
|
179
|
+
|
|
180
|
+
```ruby
|
|
181
|
+
Whatsapp::BusinessPhoneNumber::Register.call(
|
|
182
|
+
pin: "212834",
|
|
183
|
+
client: Whatsapp::Client.new(phone_id: "OTHER_PHONE_ID")
|
|
184
|
+
)
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
## Rate limits
|
|
188
|
+
|
|
189
|
+
> **`Register` / `Deregister`:** 10 requests per business number in a **72-hour moving
|
|
190
|
+
> window**. Exceeding it returns error `133016` and blocks the operation for the next
|
|
191
|
+
> 72 hours.
|
|
192
|
+
>
|
|
193
|
+
> **`RequestCode` / `VerifyCode`:** "standard Graph API rate limits", plus possible
|
|
194
|
+
> additional throttling — Meta publishes no number.
|
|
195
|
+
|
|
196
|
+
This is server-side state the gem cannot observe, so there is no client-side tracking.
|
|
197
|
+
It is also why every action validates locally first: a rejected attempt still counts
|
|
198
|
+
against the limit.
|
|
199
|
+
|
|
200
|
+
## Errors
|
|
201
|
+
|
|
202
|
+
```ruby
|
|
203
|
+
Whatsapp::BusinessPhoneNumber::Register.call(pin: "212834")
|
|
204
|
+
# => Whatsapp::BusinessPhoneNumber::Error: phone_id is required for the register edge;
|
|
205
|
+
# set it via Whatsapp.configure or Client.new(phone_id:)
|
|
206
|
+
|
|
207
|
+
# non-2xx response
|
|
208
|
+
# => Whatsapp::BusinessPhoneNumber::Error: Failed to register business phone number:
|
|
209
|
+
# 400 Bad Request - {"error":{"message":"..."}}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
Local validation failures raise `ActiveModel::ValidationError` before any request. See
|
|
213
|
+
[../errors.md](../errors.md).
|
|
214
|
+
|
|
215
|
+
## Not wrapped
|
|
216
|
+
|
|
217
|
+
- **Client-side rate-limit tracking** — server-side state the gem cannot observe.
|
|
218
|
+
- **Two-step verification PIN management.** `Register` *consumes* an existing PIN;
|
|
219
|
+
setting or changing one is a different endpoint.
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
223
|
+
**See also:** [Reading and updating the business account](account.md) ·
|
|
224
|
+
[Reading and updating the business profile](profile.md)
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
**Meta docs:**
|
|
229
|
+
[registration](https://developers.facebook.com/documentation/business-messaging/whatsapp/business-phone-numbers/registration)
|
|
230
|
+
· [request code](https://developers.facebook.com/documentation/business-messaging/whatsapp/reference/whatsapp-business-phone-number/phone-number-verification-request-code-api)
|
|
231
|
+
· [verify code](https://developers.facebook.com/documentation/business-messaging/whatsapp/reference/whatsapp-business-phone-number/verify-code-api)
|
|
232
|
+
· [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,220 @@
|
|
|
1
|
+
# Reading and Updating the Business Profile
|
|
2
|
+
|
|
3
|
+
A business phone number has a **business profile** — the public card a WhatsApp user sees
|
|
4
|
+
before they reply: the about line, the description, where the business is, how else to reach
|
|
5
|
+
it, and what industry it is in. The rest of this module drives whether a number *works*;
|
|
6
|
+
`Whatsapp::BusinessPhoneNumber::Profile` decides how it *looks*.
|
|
7
|
+
|
|
8
|
+
> **Same `phone_id` as the onboarding actions.** Meta exposes the profile as an edge on the
|
|
9
|
+
> phone number itself, so `Profile` addresses `phone_id` exactly like
|
|
10
|
+
> [`RequestCode`/`VerifyCode`/`Register`/`Deregister`](README.md) — unlike
|
|
11
|
+
> [`Account`](account.md), which is this module's one `waba_id`-scoped corner.
|
|
12
|
+
|
|
13
|
+
```ruby
|
|
14
|
+
Whatsapp.configure do |config|
|
|
15
|
+
config.api_key = ENV.fetch("WHATSAPP_API_KEY")
|
|
16
|
+
config.phone_id = ENV.fetch("WHATSAPP_PHONE_ID")
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
Whatsapp::BusinessPhoneNumber::Profile::Get.call.about # => "Open daily 9-5"
|
|
20
|
+
Whatsapp::BusinessPhoneNumber::Profile::Update.call(about: "Open 9-6") # => #<Response success=true>
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
| Method | Request | Returns |
|
|
24
|
+
| --- | --- | --- |
|
|
25
|
+
| `Get.call(client:, fields:)` | `GET /{phone_id}/whatsapp_business_profile` | `Profile::Details` |
|
|
26
|
+
| `Update.call(client:, **fields)` | `POST /{phone_id}/whatsapp_business_profile` | `BusinessPhoneNumber::Response` |
|
|
27
|
+
|
|
28
|
+
Two independent actions, so two classes — the same shape as [`Account`](account.md) and
|
|
29
|
+
[subscribed apps](../subscribed_app/README.md), not the single-object CRUD of
|
|
30
|
+
[templates](../message_templates/README.md).
|
|
31
|
+
|
|
32
|
+
## Reading the profile
|
|
33
|
+
|
|
34
|
+
```ruby
|
|
35
|
+
profile = Whatsapp::BusinessPhoneNumber::Profile::Get.call
|
|
36
|
+
|
|
37
|
+
profile.about # => "Open daily 9-5"
|
|
38
|
+
profile.vertical # => "RETAIL"
|
|
39
|
+
profile.websites # => ["https://acme.test"]
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Ask for specific fields with `fields:` — an Array or a String, comma-joined for you:
|
|
43
|
+
|
|
44
|
+
```ruby
|
|
45
|
+
Whatsapp::BusinessPhoneNumber::Profile::Get.call(fields: %w[about vertical websites])
|
|
46
|
+
Whatsapp::BusinessPhoneNumber::Profile::Get.call(fields: "about,vertical,websites")
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
`Fields::ALL` requests everything Meta publishes, without hand-typing the list:
|
|
50
|
+
|
|
51
|
+
```ruby
|
|
52
|
+
profile = Whatsapp::BusinessPhoneNumber::Profile::Get.call(
|
|
53
|
+
fields: Whatsapp::BusinessPhoneNumber::Profile::Get::Fields::ALL
|
|
54
|
+
)
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
| Field | Description |
|
|
58
|
+
| --- | --- |
|
|
59
|
+
| `messaging_product` | The messaging service — always `"whatsapp"` in practice |
|
|
60
|
+
| `about` | The text shown in the profile's About section |
|
|
61
|
+
| `address` | The business's physical address |
|
|
62
|
+
| `description` | The business description |
|
|
63
|
+
| `email` | The business's contact email address |
|
|
64
|
+
| `profile_picture_url` | URL of the current profile picture |
|
|
65
|
+
| `websites` | The business's website URLs |
|
|
66
|
+
| `vertical` | One of `Profile::Verticals::ALL` |
|
|
67
|
+
|
|
68
|
+
> `fields` is **not** validated against `Fields::ALL`. Those constants are for
|
|
69
|
+
> convenience, not enforcement — a field Meta adds later works without a gem release.
|
|
70
|
+
> Only what Meta documents as a *client-side* rule is checked locally.
|
|
71
|
+
|
|
72
|
+
Meta wraps the read in a `data` array; `Profile::Details` unwraps it for you, so there is no
|
|
73
|
+
`.data.first` to reach through. Every field is optional in the response, so anything you did
|
|
74
|
+
not request is `nil` rather than an error.
|
|
75
|
+
|
|
76
|
+
> Read-side values are never validated or normalized on the way in. A `vertical` Meta adds
|
|
77
|
+
> later still round-trips untouched, so `profile.vertical` is always exactly what the API
|
|
78
|
+
> sent — compare it against `Verticals::ALL` rather than expecting `Details` to have
|
|
79
|
+
> rejected it. Same reasoning as [`Account::Details`](account.md#status-values)' statuses and
|
|
80
|
+
> [`MessageTemplates::Response::Node#components`](../message_templates/responses.md).
|
|
81
|
+
|
|
82
|
+
## Updating the profile
|
|
83
|
+
|
|
84
|
+
```ruby
|
|
85
|
+
Whatsapp::BusinessPhoneNumber::Profile::Update.call(
|
|
86
|
+
about: "Open daily 9-6", vertical: "RETAIL", websites: ["https://acme.test"]
|
|
87
|
+
).success
|
|
88
|
+
# => true
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
| Field | Required | Rules |
|
|
92
|
+
| --- | --- | --- |
|
|
93
|
+
| `about` | no | At most **139** characters |
|
|
94
|
+
| `address` | no | At most **256** characters |
|
|
95
|
+
| `description` | no | At most **512** characters |
|
|
96
|
+
| `email` | no | At most **128** characters. Format is *not* checked — see below |
|
|
97
|
+
| `vertical` | no | One of `Verticals::ALL` — either casing accepted, emitted uppercase |
|
|
98
|
+
| `websites` | no | At most **2** URLs |
|
|
99
|
+
| `profile_picture_handle` | no | Non-empty when given. From the Resumable Upload API |
|
|
100
|
+
|
|
101
|
+
Every field is optional, but **at least one must be present** — otherwise the request would
|
|
102
|
+
spend a call to change nothing:
|
|
103
|
+
|
|
104
|
+
```ruby
|
|
105
|
+
Whatsapp::BusinessPhoneNumber::Profile::Update.call
|
|
106
|
+
# => ActiveModel::ValidationError: at least one of about, address, description, email,
|
|
107
|
+
# vertical, websites, profile_picture_handle must be provided
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
The limits and the vertical enum are checked before anything is sent:
|
|
111
|
+
|
|
112
|
+
```ruby
|
|
113
|
+
Whatsapp::BusinessPhoneNumber::Profile::Update.call(about: "a" * 140)
|
|
114
|
+
# => ActiveModel::ValidationError: About is too long (maximum is 139 characters)
|
|
115
|
+
|
|
116
|
+
Whatsapp::BusinessPhoneNumber::Profile::Update.call(vertical: "SPACESHIPS")
|
|
117
|
+
# => ActiveModel::ValidationError: Vertical is not included in the list
|
|
118
|
+
|
|
119
|
+
Whatsapp::BusinessPhoneNumber::Profile::Update.call(websites: %w[https://a.test https://b.test https://c.test])
|
|
120
|
+
# => ActiveModel::ValidationError: websites accepts at most 2 URLs
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
> **`email`'s format is deliberately not validated.** Meta checks the address server-side,
|
|
124
|
+
> and a regex here would reject perfectly valid exotic addresses. This gem checks only
|
|
125
|
+
> length. Same standing decision as `override_callback_uri` in
|
|
126
|
+
> [subscribed apps](../subscribed_app/README.md) — no speculative hardening.
|
|
127
|
+
|
|
128
|
+
`messaging_product` is always sent for you. An omitted field is compacted out of the body
|
|
129
|
+
rather than sent as `null`, which Meta would read as an instruction to blank it:
|
|
130
|
+
|
|
131
|
+
```ruby
|
|
132
|
+
Whatsapp::BusinessPhoneNumber::Profile::Update.new(about: "Open daily").serialize
|
|
133
|
+
# => { messaging_product: "whatsapp", about: "Open daily" }
|
|
134
|
+
|
|
135
|
+
Whatsapp::BusinessPhoneNumber::Profile::Update.new(vertical: "retail").serialize
|
|
136
|
+
# => { messaging_product: "whatsapp", vertical: "RETAIL" }
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Clearing the website list
|
|
140
|
+
|
|
141
|
+
An **empty array** is the one falsy-looking value that survives compaction, because that is
|
|
142
|
+
how Meta clears the list — and it counts as "changing something", so it satisfies the
|
|
143
|
+
at-least-one-field rule on its own:
|
|
144
|
+
|
|
145
|
+
```ruby
|
|
146
|
+
Whatsapp::BusinessPhoneNumber::Profile::Update.new(websites: []).serialize
|
|
147
|
+
# => { messaging_product: "whatsapp", websites: [] }
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Industry verticals
|
|
151
|
+
|
|
152
|
+
```ruby
|
|
153
|
+
Whatsapp::BusinessPhoneNumber::Profile::Verticals::ALL
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
| Constant | Values |
|
|
157
|
+
| --- | --- |
|
|
158
|
+
| `Verticals::ALL` | `ALCOHOL` `APPAREL` `AUTO` `BEAUTY` `EDU` `ENTERTAIN` `EVENT_PLAN` `FINANCE` `GOVT` `GROCERY` `HEALTH` `HOTEL` `NONPROFIT` `ONLINE_GAMBLING` `OTC_DRUGS` `OTHER` `PHYSICAL_GAMBLING` `PROF_SERVICES` `RESTAURANT` `RETAIL` `TRAVEL` |
|
|
159
|
+
|
|
160
|
+
Either casing is accepted and normalized on the way out, so `"retail"`, `:retail`, and
|
|
161
|
+
`"RETAIL"` are the same request.
|
|
162
|
+
|
|
163
|
+
### The profile picture
|
|
164
|
+
|
|
165
|
+
`profile_picture_handle` is forwarded as an opaque string. The handle itself comes from
|
|
166
|
+
Meta's **Resumable Upload API**, which this gem does not wrap:
|
|
167
|
+
|
|
168
|
+
```ruby
|
|
169
|
+
Whatsapp::BusinessPhoneNumber::Profile::Update.call(profile_picture_handle: "4::aW1hZ2UvcG5n...")
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
> **A media ID will not work here.** [`Media#upload`](../media/README.md) returns a media ID
|
|
173
|
+
> for *sending* messages — a different thing from an upload handle. Same standing exclusion
|
|
174
|
+
> as media handles for [template headers](../message_templates/README.md#not-wrapped).
|
|
175
|
+
|
|
176
|
+
`Update` returns the same `Whatsapp::BusinessPhoneNumber::Response` as the four onboarding
|
|
177
|
+
actions — the endpoint answers with a bare `{ "success": true }`, so there is no separate
|
|
178
|
+
response class for it.
|
|
179
|
+
|
|
180
|
+
## Errors
|
|
181
|
+
|
|
182
|
+
```ruby
|
|
183
|
+
Whatsapp::BusinessPhoneNumber::Profile::Get.call
|
|
184
|
+
# => Whatsapp::BusinessPhoneNumber::Error: phone_id is required for the
|
|
185
|
+
# whatsapp_business_profile edge; set it via Whatsapp.configure or Client.new(phone_id:)
|
|
186
|
+
|
|
187
|
+
# non-2xx response
|
|
188
|
+
# => Whatsapp::BusinessPhoneNumber::Error: Failed to update business profile:
|
|
189
|
+
# 400 Bad Request - {"error":{"message":"Invalid parameter: email must be a valid ..."}}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
Both actions raise `Whatsapp::BusinessPhoneNumber::Error` — the same class the onboarding
|
|
193
|
+
actions use, so one `rescue` covers the whole module. Local validation failures raise
|
|
194
|
+
`ActiveModel::ValidationError` before any request. See [../errors.md](../errors.md).
|
|
195
|
+
|
|
196
|
+
## Targeting a different number
|
|
197
|
+
|
|
198
|
+
Inject a client rather than mutating global configuration:
|
|
199
|
+
|
|
200
|
+
```ruby
|
|
201
|
+
Whatsapp::BusinessPhoneNumber::Profile::Get.call(
|
|
202
|
+
client: Whatsapp::Client.new(phone_id: "OTHER_PHONE_ID")
|
|
203
|
+
)
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
## Not wrapped
|
|
207
|
+
|
|
208
|
+
- **The Resumable Upload API** that mints a `profile_picture_handle` — the handle is accepted
|
|
209
|
+
and forwarded, but not produced.
|
|
210
|
+
- **Profile read caching.** Meta suggests caching profile reads for moderate periods;
|
|
211
|
+
invalidation is an application concern.
|
|
212
|
+
|
|
213
|
+
---
|
|
214
|
+
|
|
215
|
+
**See also:** [Reading and updating the business account](account.md) ·
|
|
216
|
+
[Registering business phone numbers](README.md)
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
**Meta docs:** <https://developers.facebook.com/documentation/business-messaging/whatsapp/reference/whatsapp-business-phone-number/whatsapp-business-profile-api>
|