ruby-whatsapp 0.4.2 → 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 +143 -0
- data/README.md +70 -7
- data/docs/README.md +4 -1
- data/docs/business_phone_number/README.md +12 -1
- data/docs/business_phone_number/contact_book.md +90 -0
- data/docs/business_phone_number/username.md +157 -0
- data/docs/configuration.md +6 -1
- data/docs/errors.md +12 -1
- data/docs/message_templates/README.md +1 -1
- data/docs/message_templates/components.md +37 -1
- data/docs/messages/README.md +86 -9
- data/docs/messages/address.md +1 -1
- data/docs/messages/audio.md +1 -1
- data/docs/messages/contacts.md +1 -1
- data/docs/messages/document.md +1 -1
- data/docs/messages/image.md +1 -1
- data/docs/messages/interactive.md +64 -3
- data/docs/messages/location.md +1 -1
- data/docs/messages/location_request.md +1 -1
- data/docs/messages/reaction.md +1 -1
- data/docs/messages/sticker.md +1 -1
- data/docs/messages/template.md +28 -1
- data/docs/messages/text.md +1 -1
- data/docs/messages/video.md +1 -1
- data/docs/schedule/README.md +183 -0
- data/docs/webhooks/README.md +20 -12
- data/docs/webhooks/business_username_updates.md +55 -0
- data/docs/webhooks/history.md +78 -15
- data/docs/webhooks/messages.md +141 -6
- data/docs/webhooks/smb_app_state_sync.md +50 -12
- data/docs/webhooks/smb_message_echoes.md +58 -21
- data/docs/webhooks/user_id_update.md +84 -0
- data/docs/webhooks/user_preferences.md +55 -16
- 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/transport.rb +6 -5
- 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 +9 -0
- 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/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/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/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 +38 -1
data/docs/errors.md
CHANGED
|
@@ -22,6 +22,7 @@ StandardError
|
|
|
22
22
|
├── Whatsapp::Media::MediaError any Media failure
|
|
23
23
|
├── Whatsapp::MessageTemplates::TemplateError any template-management failure
|
|
24
24
|
├── Whatsapp::SubscribedApp::Error any subscribed-apps failure
|
|
25
|
+
├── Whatsapp::Schedule::Error any campaign-schedule failure
|
|
25
26
|
└── Whatsapp::BusinessPhoneNumber::Error any phone-number-onboarding or account failure
|
|
26
27
|
```
|
|
27
28
|
|
|
@@ -85,9 +86,19 @@ end
|
|
|
85
86
|
A required Ruby keyword that isn't supplied raises `ArgumentError`, not a validation
|
|
86
87
|
error — it never reaches the validator:
|
|
87
88
|
|
|
89
|
+
```ruby
|
|
90
|
+
Whatsapp::Messages.send_text!(to: "+15551234567")
|
|
91
|
+
# => ArgumentError: missing keyword: :body
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
The recipient is the exception. Since a message can be addressed by phone number
|
|
95
|
+
(`to:`) *or* by [BSUID](messages/README.md#addressing-to-recipient-or-both)
|
|
96
|
+
(`recipient:`), neither keyword is individually required, so omitting both is a
|
|
97
|
+
validation failure rather than an arity one:
|
|
98
|
+
|
|
88
99
|
```ruby
|
|
89
100
|
Whatsapp::Messages.send_text!(body: "Hi")
|
|
90
|
-
# =>
|
|
101
|
+
# => ActiveModel::ValidationError: at least one of to or recipient must be provided
|
|
91
102
|
```
|
|
92
103
|
|
|
93
104
|
## API failures
|
|
@@ -191,7 +191,7 @@ templates.delete(hsm_ids: [], name: "x")
|
|
|
191
191
|
[`Media#upload`](../media/README.md), whose media IDs are for *sending*, not
|
|
192
192
|
template creation.
|
|
193
193
|
- **Undocumented button types:** `FLOW`, `MPM`, `CATALOG`, `VOICE_CALL`, `VIDEO_CALL`,
|
|
194
|
-
`POSTBACK`, `BOOKING_STATUS`, `PAYMENT_REQUEST
|
|
194
|
+
`POSTBACK`, `BOOKING_STATUS`, `PAYMENT_REQUEST`. Named in
|
|
195
195
|
Meta's Graph enum with no published field reference.
|
|
196
196
|
[`LibraryTemplate`](library.md) does accept most of them, because Meta documents them
|
|
197
197
|
by name in *that* context.
|
|
@@ -108,6 +108,7 @@ Resolved through the frozen `Button::TYPES` registry.
|
|
|
108
108
|
| `:phone_number` | `PHONE_NUMBER` | `text` ≤ 25, `phone_number` ≤ **20** |
|
|
109
109
|
| `:copy_code` | `COPY_CODE` | `example` ≤ **20** (a bare String), **no `text`** |
|
|
110
110
|
| `:otp` | `OTP` | `otp_type`, `supported_apps`, `zero_tap_terms_accepted` |
|
|
111
|
+
| `:request_contact_info` | `REQUEST_CONTACT_INFO` | **no fields** — `text` optional and only ever `"Share Contact Info"` |
|
|
111
112
|
|
|
112
113
|
```ruby
|
|
113
114
|
{ type: :quick_reply, text: "More like this" }
|
|
@@ -115,6 +116,7 @@ Resolved through the frozen `Button::TYPES` registry.
|
|
|
115
116
|
{ type: :phone_number, text: "Call", phone_number: "15550051310" }
|
|
116
117
|
{ type: :copy_code, example: "SPRING25" }
|
|
117
118
|
{ type: :otp, otp_type: "COPY_CODE" }
|
|
119
|
+
{ type: :request_contact_info }
|
|
118
120
|
```
|
|
119
121
|
|
|
120
122
|
### The URL button's single trailing variable
|
|
@@ -146,6 +148,39 @@ those labels:
|
|
|
146
148
|
# Meta supplies the label
|
|
147
149
|
```
|
|
148
150
|
|
|
151
|
+
### The request-contact-info button
|
|
152
|
+
|
|
153
|
+
Asks the recipient to share their phone number, which they hand over with one tap. It
|
|
154
|
+
takes nothing:
|
|
155
|
+
|
|
156
|
+
```ruby
|
|
157
|
+
{ type: :buttons, buttons: [{ type: :request_contact_info }] }
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
This is the counterpart to Meta's usernames rollout: once a user adopts a username their
|
|
161
|
+
phone number may stop reaching you, leaving only their
|
|
162
|
+
[BSUID](../webhooks/messages.md#business-scoped-user-ids-bsuid). It is also the only way
|
|
163
|
+
to reach such a user with a one-tap, zero-tap, or copy-code
|
|
164
|
+
[authentication template](authentication.md), since those require a phone number.
|
|
165
|
+
|
|
166
|
+
Three things about it:
|
|
167
|
+
|
|
168
|
+
- **The label is not customizable.** Meta renders it in the recipient's own app language.
|
|
169
|
+
A `text` field is accepted but must be exactly `"Share Contact Info"`, so the simplest
|
|
170
|
+
correct thing is to omit it.
|
|
171
|
+
- **`UTILITY` and `MARKETING` only** — a cross-component rule, listed below.
|
|
172
|
+
- **Nothing to fill in when sending.** Unlike a URL or copy-code button, it takes no
|
|
173
|
+
parameter values, so a template carrying one needs no `button` component in the
|
|
174
|
+
[send payload](../messages/template.md) at all.
|
|
175
|
+
|
|
176
|
+
A tap arrives as an inbound [`contacts` message](../webhooks/messages.md#contacts) whose
|
|
177
|
+
`origin` is `"contact_request"`. There is also an
|
|
178
|
+
[interactive-message form](../messages/interactive.md) of the same button, for use inside
|
|
179
|
+
the 24-hour service window.
|
|
180
|
+
|
|
181
|
+
No per-type cap is enforced: Meta publishes none, and inventing one would reject valid
|
|
182
|
+
payloads — the same reason `OTP` is uncapped. The overall 10-button limit still applies.
|
|
183
|
+
|
|
149
184
|
## Cross-component rules
|
|
150
185
|
|
|
151
186
|
Owned by `ComponentSet`, because it is the only object that can see the siblings:
|
|
@@ -155,6 +190,7 @@ Owned by `ComponentSet`, because it is the only object that can see the siblings
|
|
|
155
190
|
`LIMITED_TIME_OFFER`.
|
|
156
191
|
- `CAROUSEL` or `LIMITED_TIME_OFFER` → category must be `MARKETING`.
|
|
157
192
|
- A `LOCATION` header → category must be `UTILITY` or `MARKETING`.
|
|
193
|
+
- A `REQUEST_CONTACT_INFO` button → category must be `UTILITY` or `MARKETING`.
|
|
158
194
|
- `LIMITED_TIME_OFFER` present → `FOOTER` forbidden, `BODY` ≤ **600**, header
|
|
159
195
|
`IMAGE`/`VIDEO` only, copy-code `example` ≤ **15** and that button must be **first**.
|
|
160
196
|
|
|
@@ -170,7 +206,7 @@ enum but deliberately absent:
|
|
|
170
206
|
- **Components:** `GREETING`, `ALBUM`, `CALL_PERMISSION_REQUEST`,
|
|
171
207
|
`TAP_TARGET_CONFIGURATION`, `ATTACHMENT`.
|
|
172
208
|
- **Buttons:** `FLOW`, `MPM`, `CATALOG`, `VOICE_CALL`, `VIDEO_CALL`, `POSTBACK`,
|
|
173
|
-
`BOOKING_STATUS`, `PAYMENT_REQUEST
|
|
209
|
+
`BOOKING_STATUS`, `PAYMENT_REQUEST`. Most of these *are*
|
|
174
210
|
accepted by [`LibraryTemplate`](library.md), because Meta documents them by name in
|
|
175
211
|
that context.
|
|
176
212
|
|
data/docs/messages/README.md
CHANGED
|
@@ -61,9 +61,8 @@ Plus one endpoint that isn't a message kind at all:
|
|
|
61
61
|
|
|
62
62
|
## The shared envelope
|
|
63
63
|
|
|
64
|
-
Every kind except `mark_message_as_read` inherits `Messages::Base`, which supplies
|
|
65
|
-
|
|
66
|
-
kind-specific hash in a common envelope:
|
|
64
|
+
Every kind except `mark_message_as_read` inherits `Messages::Base`, which supplies the
|
|
65
|
+
recipient and wraps the kind-specific hash in a common envelope:
|
|
67
66
|
|
|
68
67
|
```ruby
|
|
69
68
|
{
|
|
@@ -75,6 +74,56 @@ kind-specific hash in a common envelope:
|
|
|
75
74
|
}
|
|
76
75
|
```
|
|
77
76
|
|
|
77
|
+
### Addressing: `to`, `recipient`, or both
|
|
78
|
+
|
|
79
|
+
There are two ways to name a recipient, and **at least one is required**:
|
|
80
|
+
|
|
81
|
+
| Keyword | Value |
|
|
82
|
+
| --- | --- |
|
|
83
|
+
| `to` | The recipient's phone number |
|
|
84
|
+
| `recipient` | Their [business-scoped user ID](#business-scoped-user-ids-bsuid) (BSUID), or parent BSUID |
|
|
85
|
+
|
|
86
|
+
```ruby
|
|
87
|
+
Whatsapp::Messages.send_text!(to: "+15551234567", body: "Hi") # phone number
|
|
88
|
+
Whatsapp::Messages.send_text!(recipient: "US.13491208655302741918", body: "Hi") # BSUID
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Pass both and Meta gives `to` precedence, answering as though `recipient` had not been
|
|
92
|
+
sent. Pass neither and you get an `ActiveModel::ValidationError` before any request:
|
|
93
|
+
|
|
94
|
+
```ruby
|
|
95
|
+
Whatsapp::Messages.send_text!(body: "Hi")
|
|
96
|
+
# => ActiveModel::ValidationError: at least one of to or recipient must be provided
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Whichever you omit is left out of the payload entirely, never sent as `null`.
|
|
100
|
+
|
|
101
|
+
#### Business-scoped user IDs (BSUID)
|
|
102
|
+
|
|
103
|
+
Once a WhatsApp user adopts a username, their phone number can stop appearing in your
|
|
104
|
+
webhooks — and their BSUID becomes the only identifier you have. `recipient` is how you
|
|
105
|
+
reply to them. A BSUID is a country code, a period, and up to 128 alphanumeric
|
|
106
|
+
characters (`"US.13491208655302741918"`); a
|
|
107
|
+
[parent BSUID](https://developers.facebook.com/documentation/business-messaging/whatsapp/business-scoped-user-ids#parent-business-scoped-user-ids),
|
|
108
|
+
usable across enrolled business portfolios, carries an extra `ENT.` segment
|
|
109
|
+
(`"US.ENT.11815799212886844830"`). Pass the whole value — dropping or altering any part
|
|
110
|
+
of it fails the request.
|
|
111
|
+
|
|
112
|
+
The gem does **not** format-check it. Meta describes the shape but publishes no
|
|
113
|
+
client-side rule, and the value you pass here normally comes straight off the
|
|
114
|
+
[inbound webhook side](../webhooks/messages.md#business-scoped-user-ids-bsuid), which
|
|
115
|
+
keeps it as a raw string. Replying to a customer Meta just told you about is exactly the
|
|
116
|
+
case a local regex would break.
|
|
117
|
+
|
|
118
|
+
```ruby
|
|
119
|
+
# In your webhook handler, replying to whoever wrote in
|
|
120
|
+
contact = change.value.contacts.first
|
|
121
|
+
Whatsapp::Messages.send_text!(recipient: contact.user_id, body: "Thanks for reaching out!")
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
BSUIDs are scoped to a business portfolio: a BSUID from one portfolio cannot be
|
|
125
|
+
messaged from a phone number owned by another.
|
|
126
|
+
|
|
78
127
|
Three kinds break the `type == kind` symmetry because Meta transports them as
|
|
79
128
|
interactive messages: [`address`](address.md), [`location_request`](location_request.md),
|
|
80
129
|
and of course [`interactive`](interactive.md) all serialize with `type: "interactive"`.
|
|
@@ -95,13 +144,38 @@ body shaped like:
|
|
|
95
144
|
```ruby
|
|
96
145
|
response = Whatsapp::Messages.send_text!(to: "+15551234567", body: "Hi")
|
|
97
146
|
|
|
98
|
-
response.messaging_product
|
|
99
|
-
response.messages.first.id
|
|
100
|
-
response.contacts.first.input
|
|
101
|
-
response.contacts.first.wa_id
|
|
102
|
-
response.
|
|
147
|
+
response.messaging_product # => "whatsapp"
|
|
148
|
+
response.messages.first.id # => "wamid.HBgLMTU1NTU1NTU1NTUV..."
|
|
149
|
+
response.contacts.first.input # => "+15551234567"
|
|
150
|
+
response.contacts.first.wa_id # => "15551234567"
|
|
151
|
+
response.contacts.first.user_id # => nil — see below
|
|
152
|
+
response.success # => nil for a normal send
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Which identifiers come back mirrors how you addressed the message:
|
|
156
|
+
|
|
157
|
+
| Accessor | Value |
|
|
158
|
+
| --- | --- |
|
|
159
|
+
| `input` | Exactly what you sent: the phone number, the BSUID or parent BSUID, or a group ID |
|
|
160
|
+
| `wa_id` | The phone number — **only** if you addressed it by phone number |
|
|
161
|
+
| `user_id` | The BSUID or parent BSUID — **only** if you addressed it by BSUID |
|
|
162
|
+
|
|
163
|
+
```ruby
|
|
164
|
+
response = Whatsapp::Messages.send_text!(recipient: "US.13491208655302741918", body: "Hi")
|
|
165
|
+
|
|
166
|
+
response.contacts.first.input # => "US.13491208655302741918"
|
|
167
|
+
response.contacts.first.user_id # => "US.13491208655302741918"
|
|
168
|
+
response.contacts.first.wa_id # => nil
|
|
103
169
|
```
|
|
104
170
|
|
|
171
|
+
> **The precedence trap.** Send both `to` and `recipient` and `user_id` comes back
|
|
172
|
+
> **omitted**, because `to` wins and Meta answers as though you had only sent that. So
|
|
173
|
+
> `user_id` being `nil` does not mean the recipient has no BSUID — and code that
|
|
174
|
+
> correlates a send by reading `contacts.first.user_id` must not also pass `to:`.
|
|
175
|
+
|
|
176
|
+
Unlike the webhook classes, which separate `user_id` from `parent_user_id`, the send
|
|
177
|
+
response returns either kind in the single `user_id` field.
|
|
178
|
+
|
|
105
179
|
`#success` is `nil` for a normal message send and `true`/`false` only for the
|
|
106
180
|
status-update endpoints that reply `{"success": true}` — today just
|
|
107
181
|
[`mark_message_as_read!`](mark_message_as_read.md).
|
|
@@ -110,7 +184,7 @@ status-update endpoints that reply `{"success": true}` — today just
|
|
|
110
184
|
|
|
111
185
|
| Raised | When |
|
|
112
186
|
| --- | --- |
|
|
113
|
-
| `ArgumentError` |
|
|
187
|
+
| `ArgumentError` | A required keyword is missing (the recipient is a *validation*, not an arity, concern — see below) |
|
|
114
188
|
| `ActiveModel::ValidationError` | A field fails a local validation |
|
|
115
189
|
| `Whatsapp::Messages::PayloadError` | `kind:` is unrecognized — only reachable through the `Messages.new(kind:, payload:)` form |
|
|
116
190
|
| `Whatsapp::RequestError` | The API answered with a non-2xx status |
|
|
@@ -122,6 +196,9 @@ field raises at *construction* time, before a client is ever touched:
|
|
|
122
196
|
Whatsapp::Messages.send_image!(to: "+15551234567")
|
|
123
197
|
# => ActiveModel::ValidationError: Either id or link must be present
|
|
124
198
|
|
|
199
|
+
Whatsapp::Messages.send_text!(body: "Hi")
|
|
200
|
+
# => ActiveModel::ValidationError: at least one of to or recipient must be provided
|
|
201
|
+
|
|
125
202
|
Whatsapp::Messages.new(kind: :telepathy, payload: { to: "+1" })
|
|
126
203
|
# => Whatsapp::Messages::PayloadError: Unknown message kind: :telepathy.
|
|
127
204
|
# Known kinds: text, image, audio, video, document, sticker, contacts,
|
data/docs/messages/address.md
CHANGED
|
@@ -20,7 +20,7 @@ Whatsapp::Messages.send_address!(
|
|
|
20
20
|
|
|
21
21
|
| Field | Required | Rules |
|
|
22
22
|
| --- | --- | --- |
|
|
23
|
-
| `to`
|
|
23
|
+
| `to` / `recipient` | at least one | Phone number and/or [BSUID](README.md#addressing-to-recipient-or-both) |
|
|
24
24
|
| `body` | yes | The prompt shown above the form |
|
|
25
25
|
| `country` | yes | `"IN"` or `"SG"` only |
|
|
26
26
|
| `footer` | no | Small print under the form |
|
data/docs/messages/audio.md
CHANGED
|
@@ -10,7 +10,7 @@ Whatsapp::Messages.send_audio!(to: "+15551234567", link: "https://example.com/cl
|
|
|
10
10
|
|
|
11
11
|
| Field | Required | Rules |
|
|
12
12
|
| --- | --- | --- |
|
|
13
|
-
| `to`
|
|
13
|
+
| `to` / `recipient` | at least one | Phone number and/or [BSUID](README.md#addressing-to-recipient-or-both) |
|
|
14
14
|
| `id` | one of | A media ID from [`Media#upload`](../media/README.md) |
|
|
15
15
|
| `link` | one of | A publicly reachable HTTPS URL |
|
|
16
16
|
|
data/docs/messages/contacts.md
CHANGED
|
@@ -15,7 +15,7 @@ Whatsapp::Messages.send_contacts!(
|
|
|
15
15
|
|
|
16
16
|
| Field | Required | Rules |
|
|
17
17
|
| --- | --- | --- |
|
|
18
|
-
| `to`
|
|
18
|
+
| `to` / `recipient` | at least one | Phone number and/or [BSUID](README.md#addressing-to-recipient-or-both) |
|
|
19
19
|
| `contacts` | yes | An array containing **exactly one** contact |
|
|
20
20
|
|
|
21
21
|
> **Exactly one contact.** The Cloud API currently accepts a single contact per
|
data/docs/messages/document.md
CHANGED
|
@@ -15,7 +15,7 @@ Whatsapp::Messages.send_document!(
|
|
|
15
15
|
|
|
16
16
|
| Field | Required | Rules |
|
|
17
17
|
| --- | --- | --- |
|
|
18
|
-
| `to`
|
|
18
|
+
| `to` / `recipient` | at least one | Phone number and/or [BSUID](README.md#addressing-to-recipient-or-both) |
|
|
19
19
|
| `id` | one of | A media ID from [`Media#upload`](../media/README.md) |
|
|
20
20
|
| `link` | one of | A publicly reachable HTTPS URL |
|
|
21
21
|
| `caption` | no | Max **1024** characters |
|
data/docs/messages/image.md
CHANGED
|
@@ -14,7 +14,7 @@ Whatsapp::Messages.send_image!(
|
|
|
14
14
|
|
|
15
15
|
| Field | Required | Rules |
|
|
16
16
|
| --- | --- | --- |
|
|
17
|
-
| `to`
|
|
17
|
+
| `to` / `recipient` | at least one | Phone number and/or [BSUID](README.md#addressing-to-recipient-or-both) |
|
|
18
18
|
| `id` | one of | A media ID from [`Media#upload`](../media/README.md) |
|
|
19
19
|
| `link` | one of | A publicly reachable HTTPS URL |
|
|
20
20
|
| `caption` | no | Max **1024** characters |
|
|
@@ -14,15 +14,16 @@ Unlike every other kind, `Interactive` is a **single class** that dispatches on
|
|
|
14
14
|
| `:url_button` | `"cta_url"` | [A link rendered as a button](#cta-url-button) |
|
|
15
15
|
| `:media_carousel` | `"carousel"` | [2–10 swipeable media cards](#media-carousel) |
|
|
16
16
|
| `:product_carousel` | `"product_list"` | [2–10 catalog product cards](#product-carousel) |
|
|
17
|
+
| `:request_contact_info` | `"request_contact_info"` | [A one-tap "share my number" button](#request-contact-info) |
|
|
17
18
|
|
|
18
19
|
## Shared fields
|
|
19
20
|
|
|
20
21
|
| Field | Required | Rules |
|
|
21
22
|
| --- | --- | --- |
|
|
22
|
-
| `to`
|
|
23
|
-
| `type` | yes | One of the
|
|
23
|
+
| `to` / `recipient` | at least one | Phone number and/or [BSUID](README.md#addressing-to-recipient-or-both) |
|
|
24
|
+
| `type` | yes | One of the six keys above (Symbol or String) |
|
|
24
25
|
| `body` | yes | Body text, max **1024** characters |
|
|
25
|
-
| `action` |
|
|
26
|
+
| `action` | usually | Shape depends on `type` — see each section. Omit it only for `:request_contact_info`, the one kind with nothing to supply |
|
|
26
27
|
| `header` | no | `{ type: "text"\|"image"\|"video"\|"document", ... }` |
|
|
27
28
|
| `footer` | no | `{ text: "..." }`, max **60** characters |
|
|
28
29
|
|
|
@@ -253,6 +254,66 @@ server-side.
|
|
|
253
254
|
|
|
254
255
|
---
|
|
255
256
|
|
|
257
|
+
## Request contact info
|
|
258
|
+
|
|
259
|
+
Asks the recipient to share their phone number, which they hand over with one tap. The
|
|
260
|
+
one action kind that takes no `action:` — there is nothing to configure:
|
|
261
|
+
|
|
262
|
+
```ruby
|
|
263
|
+
Whatsapp::Messages.send_interactive!(
|
|
264
|
+
recipient: "US.13491208655302741918",
|
|
265
|
+
type: :request_contact_info,
|
|
266
|
+
body: "Mind sharing your number so we can text you the confirmation?"
|
|
267
|
+
)
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
```json
|
|
271
|
+
{
|
|
272
|
+
"messaging_product": "whatsapp",
|
|
273
|
+
"recipient_type": "individual",
|
|
274
|
+
"recipient": "US.13491208655302741918",
|
|
275
|
+
"type": "interactive",
|
|
276
|
+
"interactive": {
|
|
277
|
+
"type": "request_contact_info",
|
|
278
|
+
"body": { "text": "Mind sharing your number so we can text you the confirmation?" },
|
|
279
|
+
"action": { "name": "request_contact_info" }
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
Note `interactive.type` and `action.name` are the same literal — this is the only kind
|
|
285
|
+
where the registry key and the wire value coincide.
|
|
286
|
+
|
|
287
|
+
Use this inside the 24-hour service window; to *open* a conversation with the same ask,
|
|
288
|
+
use the [`REQUEST_CONTACT_INFO` template button](../message_templates/components.md#the-request-contact-info-button)
|
|
289
|
+
instead.
|
|
290
|
+
|
|
291
|
+
Why you would: once a WhatsApp user adopts a username, their phone number can stop
|
|
292
|
+
appearing in your webhooks, leaving only their
|
|
293
|
+
[BSUID](README.md#business-scoped-user-ids-bsuid) — which is enough to keep messaging
|
|
294
|
+
them, but not enough for a one-tap, zero-tap, or copy-code
|
|
295
|
+
[authentication template](../message_templates/authentication.md). This is how you ask
|
|
296
|
+
for the number back.
|
|
297
|
+
|
|
298
|
+
A tap arrives as an inbound [`contacts` message](../webhooks/messages.md#contacts) whose
|
|
299
|
+
`origin` is `"contact_request"`:
|
|
300
|
+
|
|
301
|
+
```ruby
|
|
302
|
+
when "messages"
|
|
303
|
+
change.value.messages.each do |message|
|
|
304
|
+
next unless message.type == "contacts"
|
|
305
|
+
|
|
306
|
+
shared = message.contacts.first
|
|
307
|
+
next unless shared.origin == "contact_request"
|
|
308
|
+
|
|
309
|
+
Customer.find_by(bsuid: message.from_user_id)
|
|
310
|
+
&.update!(wa_id: shared.phones.first&.wa_id)
|
|
311
|
+
end
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
Meta's published example shows no `header` or `footer`; both are accepted here and
|
|
315
|
+
omitted from the payload when absent, as with every other kind.
|
|
316
|
+
|
|
256
317
|
## Validation errors
|
|
257
318
|
|
|
258
319
|
`Interactive` itself validates `type`, `body`, and `action` presence at construction.
|
data/docs/messages/location.md
CHANGED
|
@@ -17,7 +17,7 @@ Whatsapp::Messages.send_location!(
|
|
|
17
17
|
|
|
18
18
|
| Field | Required | Rules |
|
|
19
19
|
| --- | --- | --- |
|
|
20
|
-
| `to`
|
|
20
|
+
| `to` / `recipient` | at least one | Phone number and/or [BSUID](README.md#addressing-to-recipient-or-both) |
|
|
21
21
|
| `latitude` | yes | Numeric |
|
|
22
22
|
| `longitude` | yes | Numeric |
|
|
23
23
|
| `name` | no | Label shown above the pin |
|
|
@@ -15,7 +15,7 @@ Whatsapp::Messages.send_location_request!(
|
|
|
15
15
|
|
|
16
16
|
| Field | Required | Rules |
|
|
17
17
|
| --- | --- | --- |
|
|
18
|
-
| `to`
|
|
18
|
+
| `to` / `recipient` | at least one | Phone number and/or [BSUID](README.md#addressing-to-recipient-or-both) |
|
|
19
19
|
| `body` | yes | The prompt text, max **1024** characters |
|
|
20
20
|
|
|
21
21
|
## Serialized payload
|
data/docs/messages/reaction.md
CHANGED
|
@@ -11,7 +11,7 @@ Whatsapp::Messages.send_reaction!(to: "+15551234567", message_id: "wamid.HBg..."
|
|
|
11
11
|
|
|
12
12
|
| Field | Required | Rules |
|
|
13
13
|
| --- | --- | --- |
|
|
14
|
-
| `to`
|
|
14
|
+
| `to` / `recipient` | at least one | Phone number and/or [BSUID](README.md#addressing-to-recipient-or-both) |
|
|
15
15
|
| `message_id` | yes | The WAMID of the message being reacted to |
|
|
16
16
|
| `emoji` | yes | A single emoji, or `""` to remove |
|
|
17
17
|
|
data/docs/messages/sticker.md
CHANGED
|
@@ -10,7 +10,7 @@ Whatsapp::Messages.send_sticker!(to: "+15551234567", id: "STICKER_MEDIA_ID")
|
|
|
10
10
|
|
|
11
11
|
| Field | Required | Rules |
|
|
12
12
|
| --- | --- | --- |
|
|
13
|
-
| `to`
|
|
13
|
+
| `to` / `recipient` | at least one | Phone number and/or [BSUID](README.md#addressing-to-recipient-or-both) |
|
|
14
14
|
| `id` | one of | A media ID from [`Media#upload`](../media/README.md) |
|
|
15
15
|
| `link` | one of | A publicly reachable HTTPS URL |
|
|
16
16
|
|
data/docs/messages/template.md
CHANGED
|
@@ -23,7 +23,7 @@ Whatsapp::Messages.send_template!(
|
|
|
23
23
|
|
|
24
24
|
| Field | Required | Rules |
|
|
25
25
|
| --- | --- | --- |
|
|
26
|
-
| `to`
|
|
26
|
+
| `to` / `recipient` | at least one | Phone number and/or [BSUID](README.md#addressing-to-recipient-or-both) |
|
|
27
27
|
| `name` | yes | The approved template's name |
|
|
28
28
|
| `language` | yes | `{ code: "en_US" }` — validated against the supported list |
|
|
29
29
|
| `components` | no | Array of components supplying placeholder values. Defaults to `[]` |
|
|
@@ -36,6 +36,33 @@ Whatsapp::Utils::LanguageCodes.valid?("en_US") # => true
|
|
|
36
36
|
Whatsapp::Utils::LanguageCodes.all.size # => 71
|
|
37
37
|
```
|
|
38
38
|
|
|
39
|
+
### Authentication templates cannot go to a BSUID
|
|
40
|
+
|
|
41
|
+
One-tap, zero-tap, and copy-code
|
|
42
|
+
[authentication templates](../message_templates/authentication.md) require the
|
|
43
|
+
recipient's **phone number**. Address one to a
|
|
44
|
+
[BSUID](README.md#business-scoped-user-ids-bsuid) via `recipient:` and Meta answers
|
|
45
|
+
with error `131062`:
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
Business-scoped User ID (BSUID) recipients are not supported for this message.
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
The gem cannot catch this locally, and deliberately does not try. A template is
|
|
52
|
+
addressed at send time by `name` and `language` only — `Messages::Template` never
|
|
53
|
+
learns its own category, and the OTP configuration lives in the WABA. Guessing from
|
|
54
|
+
`components` would be worse than not guessing: a `copy_code` sub-type appears both on
|
|
55
|
+
copy-code authentication templates *and* on limited-time-offer marketing templates,
|
|
56
|
+
which have no such restriction, so any local rule would reject valid sends.
|
|
57
|
+
|
|
58
|
+
If you need to reach a username-only user with an authentication template, ask for
|
|
59
|
+
their phone number first — see the `REQUEST_CONTACT_INFO` button in
|
|
60
|
+
[components.md](../message_templates/components.md).
|
|
61
|
+
|
|
62
|
+
> Meta names one-tap, zero-tap, and copy-code specifically. Whether a plain
|
|
63
|
+
> `NO_BUTTONS` authentication template is also refused is **not documented** — treat it
|
|
64
|
+
> as untested rather than assuming either way.
|
|
65
|
+
|
|
39
66
|
## Components
|
|
40
67
|
|
|
41
68
|
A component supplies the *values* for the placeholders the template defines. It has a
|
data/docs/messages/text.md
CHANGED
|
@@ -12,7 +12,7 @@ Whatsapp::Messages.send_text!(to: "+15551234567", body: "Hello from ruby-whatsap
|
|
|
12
12
|
|
|
13
13
|
| Field | Required | Rules |
|
|
14
14
|
| --- | --- | --- |
|
|
15
|
-
| `to`
|
|
15
|
+
| `to` / `recipient` | at least one | Phone number and/or [BSUID](README.md#addressing-to-recipient-or-both) |
|
|
16
16
|
| `body` | yes | Max **4096** characters |
|
|
17
17
|
| `preview_url` | no | `true` or `false`. **Defaults to `true`** |
|
|
18
18
|
|
data/docs/messages/video.md
CHANGED
|
@@ -10,7 +10,7 @@ Whatsapp::Messages.send_video!(to: "+15551234567", id: "1234567890", caption: "D
|
|
|
10
10
|
|
|
11
11
|
| Field | Required | Rules |
|
|
12
12
|
| --- | --- | --- |
|
|
13
|
-
| `to`
|
|
13
|
+
| `to` / `recipient` | at least one | Phone number and/or [BSUID](README.md#addressing-to-recipient-or-both) |
|
|
14
14
|
| `id` | one of | A media ID from [`Media#upload`](../media/README.md) |
|
|
15
15
|
| `link` | one of | A publicly reachable HTTPS URL |
|
|
16
16
|
| `caption` | no | Max **1024** characters |
|