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
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# Mark Message As Read
|
|
2
|
+
|
|
3
|
+
Closes the read-receipt loop — this is what turns on the blue "seen" checkmarks on the
|
|
4
|
+
customer's side. Marking one message read also marks **every earlier message in that
|
|
5
|
+
conversation** as read.
|
|
6
|
+
|
|
7
|
+
```ruby
|
|
8
|
+
Whatsapp::Messages.mark_message_as_read!(
|
|
9
|
+
message_id: "wamid.HBgLMTY1MDM4Nzk0MzkVAgARGBJDQjZCMzlEQUE4OTJBMTE4RTUA"
|
|
10
|
+
)
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Fields
|
|
14
|
+
|
|
15
|
+
| Field | Required | Rules |
|
|
16
|
+
| --- | --- | --- |
|
|
17
|
+
| `message_id` | yes | The WAMID of an inbound message |
|
|
18
|
+
| `client` | no | Defaults to a new `Whatsapp::Client` |
|
|
19
|
+
|
|
20
|
+
`message_id` is checked for presence only — there is no WAMID format check, so an
|
|
21
|
+
invalid ID surfaces server-side as error `131009`.
|
|
22
|
+
|
|
23
|
+
> **30-day window.** Meta requires the call within 30 days of receiving the message.
|
|
24
|
+
> Past that, the request fails.
|
|
25
|
+
|
|
26
|
+
## Why it isn't a `send_<kind>!` method
|
|
27
|
+
|
|
28
|
+
Every other kind shares the `to` / `recipient_type` / `type` envelope from
|
|
29
|
+
`Messages::Base`. This endpoint's payload is flat — no recipient, no type — so
|
|
30
|
+
`MarkMessageAsRead` doesn't inherit `Base` at all and isn't registered in
|
|
31
|
+
`Messages::KINDS`. Rather than carve an exception into the loop that generates
|
|
32
|
+
`send_<kind>!` methods, it gets its own hand-written class method.
|
|
33
|
+
|
|
34
|
+
## Serialized payload
|
|
35
|
+
|
|
36
|
+
```ruby
|
|
37
|
+
Whatsapp::Messages::MarkMessageAsRead.new(message_id: "wamid.HBg...").serialize
|
|
38
|
+
# => { messaging_product: "whatsapp", status: "read", message_id: "wamid.HBg..." }
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## The response
|
|
42
|
+
|
|
43
|
+
This endpoint replies `{"success": true}` rather than the usual
|
|
44
|
+
`{messaging_product, contacts, messages}`, so the shared `Messages::Response` carries
|
|
45
|
+
it on `#success`:
|
|
46
|
+
|
|
47
|
+
```ruby
|
|
48
|
+
result = Whatsapp::Messages.mark_message_as_read!(message_id: "wamid.HBg...")
|
|
49
|
+
|
|
50
|
+
result.success # => true
|
|
51
|
+
result.messages # => [] — nothing to report for this endpoint
|
|
52
|
+
result.contacts # => []
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Errors
|
|
56
|
+
|
|
57
|
+
```ruby
|
|
58
|
+
Whatsapp::Messages::MarkMessageAsRead.new(message_id: "")
|
|
59
|
+
# => ActiveModel::ValidationError: Message can't be blank
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
A non-2xx response raises `Whatsapp::RequestError: Failed to mark message as read: ...`.
|
|
63
|
+
|
|
64
|
+
## Typical use
|
|
65
|
+
|
|
66
|
+
Call it from your webhook handler as soon as an inbound message is deserialized:
|
|
67
|
+
|
|
68
|
+
```ruby
|
|
69
|
+
notification.entry.each do |entry|
|
|
70
|
+
entry.changes.each do |change|
|
|
71
|
+
next unless change.field == "messages"
|
|
72
|
+
|
|
73
|
+
change.value.messages.each do |message|
|
|
74
|
+
Whatsapp::Messages.mark_message_as_read!(message_id: message.id)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
See [../webhooks/messages.md](../webhooks/messages.md) for the inbound side.
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
**Meta docs:** <https://developers.facebook.com/documentation/business-messaging/whatsapp/messages/mark-message-as-read>
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Reaction Messages
|
|
2
|
+
|
|
3
|
+
Acknowledges a previous message with an emoji, mirroring WhatsApp's native
|
|
4
|
+
tap-and-hold reaction, without cluttering the thread with a new text message.
|
|
5
|
+
|
|
6
|
+
```ruby
|
|
7
|
+
Whatsapp::Messages.send_reaction!(to: "+15551234567", message_id: "wamid.HBg...", emoji: "👍")
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
## Fields
|
|
11
|
+
|
|
12
|
+
| Field | Required | Rules |
|
|
13
|
+
| --- | --- | --- |
|
|
14
|
+
| `to` | yes | The recipient's phone number |
|
|
15
|
+
| `message_id` | yes | The WAMID of the message being reacted to |
|
|
16
|
+
| `emoji` | yes | A single emoji, or `""` to remove |
|
|
17
|
+
|
|
18
|
+
`emoji` is checked against Ruby's `\p{Emoji}` property, so a plain word is rejected
|
|
19
|
+
locally rather than by Meta. `nil` is invalid; the empty string is not.
|
|
20
|
+
|
|
21
|
+
## Removing a reaction
|
|
22
|
+
|
|
23
|
+
Sending an empty `emoji` removes a reaction you previously sent to that message:
|
|
24
|
+
|
|
25
|
+
```ruby
|
|
26
|
+
Whatsapp::Messages.send_reaction!(to: "+15551234567", message_id: "wamid.HBg...", emoji: "")
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Serialized payload
|
|
30
|
+
|
|
31
|
+
```ruby
|
|
32
|
+
Whatsapp::Messages::Reaction.new(
|
|
33
|
+
to: "+15551234567", message_id: "wamid.HBg...", emoji: "\u{1F44D}"
|
|
34
|
+
).serialize
|
|
35
|
+
# => {
|
|
36
|
+
# messaging_product: "whatsapp", recipient_type: "individual", to: "+15551234567", type: "reaction",
|
|
37
|
+
# reaction: { message_id: "wamid.HBg...", emoji: "👍" }
|
|
38
|
+
# }
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
The `reaction` hash is not compacted — both keys always ship, which is what makes the
|
|
42
|
+
empty-string removal work.
|
|
43
|
+
|
|
44
|
+
## Validation errors
|
|
45
|
+
|
|
46
|
+
```ruby
|
|
47
|
+
Whatsapp::Messages.send_reaction!(to: "+15551234567", message_id: "wamid.HBg...", emoji: "nope")
|
|
48
|
+
# => ActiveModel::ValidationError: Emoji must be a valid emoji character
|
|
49
|
+
|
|
50
|
+
Whatsapp::Messages.send_reaction!(to: "+15551234567", message_id: "wamid.HBg...", emoji: nil)
|
|
51
|
+
# => ActiveModel::ValidationError: Emoji can't be nil
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
`message_id` is checked for presence only — an existent-looking but wrong WAMID
|
|
55
|
+
surfaces server-side as error `131009`.
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
**Meta docs:** <https://developers.facebook.com/documentation/business-messaging/whatsapp/messages/reaction-messages>
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Sticker Messages
|
|
2
|
+
|
|
3
|
+
Sends a WhatsApp sticker asset (a WebP image, animated or static).
|
|
4
|
+
|
|
5
|
+
```ruby
|
|
6
|
+
Whatsapp::Messages.send_sticker!(to: "+15551234567", id: "STICKER_MEDIA_ID")
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Fields
|
|
10
|
+
|
|
11
|
+
| Field | Required | Rules |
|
|
12
|
+
| --- | --- | --- |
|
|
13
|
+
| `to` | yes | The recipient's phone number |
|
|
14
|
+
| `id` | one of | A media ID from [`Media#upload`](../media/README.md) |
|
|
15
|
+
| `link` | one of | A publicly reachable HTTPS URL |
|
|
16
|
+
|
|
17
|
+
**Either `id` or `link` is required** — prefer `id`. Stickers are almost always
|
|
18
|
+
reused across many conversations, so uploading once and holding the ID avoids
|
|
19
|
+
re-fetching the same asset on every send.
|
|
20
|
+
|
|
21
|
+
## Serialized payload
|
|
22
|
+
|
|
23
|
+
```ruby
|
|
24
|
+
Whatsapp::Messages::Sticker.new(to: "+15551234567", id: "STICKER_MEDIA_ID").serialize
|
|
25
|
+
# => {
|
|
26
|
+
# messaging_product: "whatsapp", recipient_type: "individual", to: "+15551234567", type: "sticker",
|
|
27
|
+
# sticker: { id: "STICKER_MEDIA_ID" }
|
|
28
|
+
# }
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Validation errors
|
|
32
|
+
|
|
33
|
+
```ruby
|
|
34
|
+
Whatsapp::Messages.send_sticker!(to: "+15551234567")
|
|
35
|
+
# => ActiveModel::ValidationError: Either id or link must be present
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
**Meta docs:** <https://developers.facebook.com/documentation/business-messaging/whatsapp/messages/sticker-messages>
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# Template Messages
|
|
2
|
+
|
|
3
|
+
The **only** way to message a user outside the 24-hour customer-service window.
|
|
4
|
+
Marketing, utility, and authentication templates must be created and approved by Meta
|
|
5
|
+
before they can be sent.
|
|
6
|
+
|
|
7
|
+
> **Two different APIs.** This page covers *sending* an already-approved template
|
|
8
|
+
> (`POST /{PHONE_ID}/messages`). Creating, editing, and deleting the templates
|
|
9
|
+
> themselves is a separate API addressing `waba_id` — see
|
|
10
|
+
> [../message_templates/](../message_templates/README.md). The component schemas are
|
|
11
|
+
> **not** interchangeable: management uses `text` + `example` with uppercase
|
|
12
|
+
> `HEADER`/`BODY`/`FOOTER`, sending uses `parameters` with lowercase singular types.
|
|
13
|
+
|
|
14
|
+
```ruby
|
|
15
|
+
Whatsapp::Messages.send_template!(
|
|
16
|
+
to: "+15551234567",
|
|
17
|
+
name: "order_confirmation",
|
|
18
|
+
language: { code: "en_US" }
|
|
19
|
+
)
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Fields
|
|
23
|
+
|
|
24
|
+
| Field | Required | Rules |
|
|
25
|
+
| --- | --- | --- |
|
|
26
|
+
| `to` | yes | The recipient's phone number |
|
|
27
|
+
| `name` | yes | The approved template's name |
|
|
28
|
+
| `language` | yes | `{ code: "en_US" }` — validated against the supported list |
|
|
29
|
+
| `components` | no | Array of components supplying placeholder values. Defaults to `[]` |
|
|
30
|
+
|
|
31
|
+
`language.code` is checked against `Whatsapp::Utils::LanguageCodes` (71 codes), so a
|
|
32
|
+
typo raises locally instead of coming back as a Meta error:
|
|
33
|
+
|
|
34
|
+
```ruby
|
|
35
|
+
Whatsapp::Utils::LanguageCodes.valid?("en_US") # => true
|
|
36
|
+
Whatsapp::Utils::LanguageCodes.all.size # => 71
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Components
|
|
40
|
+
|
|
41
|
+
A component supplies the *values* for the placeholders the template defines. It has a
|
|
42
|
+
`type`, and for buttons a `sub_type` and `index`.
|
|
43
|
+
|
|
44
|
+
| `type` | Purpose |
|
|
45
|
+
| --- | --- |
|
|
46
|
+
| `"header"` | Values for the header's placeholder |
|
|
47
|
+
| `"body"` | Values for the body's placeholders |
|
|
48
|
+
| `"button"` | Values for a dynamic button — also needs `sub_type` and `index` |
|
|
49
|
+
|
|
50
|
+
Each component holds `parameters`, and each parameter has a `type` selecting exactly
|
|
51
|
+
one value key:
|
|
52
|
+
|
|
53
|
+
| Parameter `type` | Value key | Shape |
|
|
54
|
+
| --- | --- | --- |
|
|
55
|
+
| `"text"` | `text` | String |
|
|
56
|
+
| `"currency"` | `currency` | `{ fallback_value:, code:, amount_1000: }` |
|
|
57
|
+
| `"date_time"` | `date_time` | `{ fallback_value: }` |
|
|
58
|
+
| `"image"` | `image` | `{ link: }` or `{ id: }` |
|
|
59
|
+
| `"document"` | `document` | `{ link: }` or `{ id: }` |
|
|
60
|
+
| `"video"` | `video` | `{ link: }` or `{ id: }` |
|
|
61
|
+
| `"location"` | `location` | `{ latitude:, longitude:, name:, address: }` |
|
|
62
|
+
| `"payload"` | `payload` | String — a quick-reply button's payload |
|
|
63
|
+
|
|
64
|
+
> Only `type` is validated. There is no cross-field check that the matching value key
|
|
65
|
+
> is populated, so `{ type: "text" }` with no `text` serializes to a bare
|
|
66
|
+
> `{ type: "text" }` and is rejected by Meta rather than locally.
|
|
67
|
+
|
|
68
|
+
## Examples
|
|
69
|
+
|
|
70
|
+
**Body placeholders:**
|
|
71
|
+
|
|
72
|
+
```ruby
|
|
73
|
+
Whatsapp::Messages.send_template!(
|
|
74
|
+
to: "+15551234567",
|
|
75
|
+
name: "order_confirmation",
|
|
76
|
+
language: { code: "en_US" },
|
|
77
|
+
components: [
|
|
78
|
+
{ type: "body", parameters: [{ type: "text", text: "Jane" },
|
|
79
|
+
{ type: "text", text: "860198-230332" }] },
|
|
80
|
+
]
|
|
81
|
+
)
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
**Media header plus body:**
|
|
85
|
+
|
|
86
|
+
```ruby
|
|
87
|
+
Whatsapp::Messages.send_template!(
|
|
88
|
+
to: "+15551234567",
|
|
89
|
+
name: "shipping_update",
|
|
90
|
+
language: { code: "en_US" },
|
|
91
|
+
components: [
|
|
92
|
+
{ type: "header", parameters: [{ type: "image", image: { link: "https://example.com/box.jpg" } }] },
|
|
93
|
+
{ type: "body", parameters: [{ type: "text", text: "Jane" }] },
|
|
94
|
+
]
|
|
95
|
+
)
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
**A dynamic URL button** — `sub_type` and `index` identify which button in the
|
|
99
|
+
template's button row is being filled:
|
|
100
|
+
|
|
101
|
+
```ruby
|
|
102
|
+
Whatsapp::Messages.send_template!(
|
|
103
|
+
to: "+15551234567",
|
|
104
|
+
name: "order_confirmation",
|
|
105
|
+
language: { code: "en_US" },
|
|
106
|
+
components: [
|
|
107
|
+
{ type: "body", parameters: [{ type: "text", text: "Jane" }] },
|
|
108
|
+
{ type: "button", sub_type: "url", index: 0,
|
|
109
|
+
parameters: [{ type: "text", text: "1234" }] },
|
|
110
|
+
]
|
|
111
|
+
)
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
**A currency and a date:**
|
|
115
|
+
|
|
116
|
+
```ruby
|
|
117
|
+
components: [
|
|
118
|
+
{ type: "body", parameters: [
|
|
119
|
+
{ type: "currency", currency: { fallback_value: "$100.99", code: "USD", amount_1000: 100_990 } },
|
|
120
|
+
{ type: "date_time", date_time: { fallback_value: "February 25, 1977" } },
|
|
121
|
+
] },
|
|
122
|
+
]
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Serialized payload
|
|
126
|
+
|
|
127
|
+
```ruby
|
|
128
|
+
Whatsapp::Messages::Template.new(
|
|
129
|
+
to: "+15551234567",
|
|
130
|
+
name: "order_confirmation",
|
|
131
|
+
language: { code: "en_US" },
|
|
132
|
+
components: [
|
|
133
|
+
{ type: "body", parameters: [{ type: "text", text: "Jane" }, { type: "text", text: "#1234" }] },
|
|
134
|
+
]
|
|
135
|
+
).serialize
|
|
136
|
+
# => {
|
|
137
|
+
# messaging_product: "whatsapp", recipient_type: "individual", to: "+15551234567", type: "template",
|
|
138
|
+
# template: {
|
|
139
|
+
# name: "order_confirmation",
|
|
140
|
+
# language: { code: "en_US" },
|
|
141
|
+
# components: [{ type: "body", parameters: [{ type: "text", text: "Jane" },
|
|
142
|
+
# { type: "text", text: "#1234" }] }]
|
|
143
|
+
# }
|
|
144
|
+
# }
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
With no components, the `components` key is dropped entirely rather than sent empty.
|
|
148
|
+
|
|
149
|
+
## Validation errors
|
|
150
|
+
|
|
151
|
+
```ruby
|
|
152
|
+
Whatsapp::Messages.send_template!(to: "+15551234567", name: "x", language: { code: "klingon" })
|
|
153
|
+
# => ActiveModel::ValidationError: Code is not a valid WhatsApp language code.
|
|
154
|
+
# Supported codes: af, sq, ar, az, bn, bg, ca, zh_CN, ...
|
|
155
|
+
|
|
156
|
+
Whatsapp::Messages.send_template!(to: "+15551234567", name: "", language: { code: "en_US" })
|
|
157
|
+
# => ActiveModel::ValidationError: Name can't be blank
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
`name` is checked for presence only — an unapproved or misspelled template name comes
|
|
161
|
+
back from Meta, not from the validator.
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
**Meta docs:** <https://developers.facebook.com/documentation/business-messaging/whatsapp/messages/template-messages>
|
|
166
|
+
· [supported languages](https://developers.facebook.com/documentation/business-messaging/whatsapp/templates/supported-languages)
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Text Messages
|
|
2
|
+
|
|
3
|
+
The default way to carry a conversational reply or notice. Most other kinds exist
|
|
4
|
+
because they need to express something plain text can't — media, buttons, structured
|
|
5
|
+
data.
|
|
6
|
+
|
|
7
|
+
```ruby
|
|
8
|
+
Whatsapp::Messages.send_text!(to: "+15551234567", body: "Hello from ruby-whatsapp!")
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Fields
|
|
12
|
+
|
|
13
|
+
| Field | Required | Rules |
|
|
14
|
+
| --- | --- | --- |
|
|
15
|
+
| `to` | yes | The recipient's phone number |
|
|
16
|
+
| `body` | yes | Max **4096** characters |
|
|
17
|
+
| `preview_url` | no | `true` or `false`. **Defaults to `true`** |
|
|
18
|
+
|
|
19
|
+
`preview_url` controls whether WhatsApp renders a link preview card for the first URL
|
|
20
|
+
in the body. Turn it off for transactional messages where a preview would be noise:
|
|
21
|
+
|
|
22
|
+
```ruby
|
|
23
|
+
Whatsapp::Messages.send_text!(
|
|
24
|
+
to: "+15551234567",
|
|
25
|
+
body: "Track your order: https://example.com/orders/1234",
|
|
26
|
+
preview_url: false
|
|
27
|
+
)
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Serialized payload
|
|
31
|
+
|
|
32
|
+
```ruby
|
|
33
|
+
Whatsapp::Messages::Text.new(to: "+15551234567", body: "Hello from ruby-whatsapp!").serialize
|
|
34
|
+
# => {
|
|
35
|
+
# messaging_product: "whatsapp", recipient_type: "individual", to: "+15551234567", type: "text",
|
|
36
|
+
# text: { body: "Hello from ruby-whatsapp!", preview_url: true }
|
|
37
|
+
# }
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
The `text` hash is deliberately **not** compacted, so `preview_url` is always present
|
|
41
|
+
on the wire even when it holds its default.
|
|
42
|
+
|
|
43
|
+
## Validation errors
|
|
44
|
+
|
|
45
|
+
```ruby
|
|
46
|
+
Whatsapp::Messages.send_text!(to: "+15551234567", body: "")
|
|
47
|
+
# => ActiveModel::ValidationError: Body can't be blank
|
|
48
|
+
|
|
49
|
+
Whatsapp::Messages.send_text!(to: "+15551234567", body: "x" * 4097)
|
|
50
|
+
# => ActiveModel::ValidationError: Body is too long (maximum is 4096 characters)
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
**Meta docs:** <https://developers.facebook.com/documentation/business-messaging/whatsapp/messages/text-messages>
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Video Messages
|
|
2
|
+
|
|
3
|
+
Shares a video — an instructional clip, a product demo, an unboxing.
|
|
4
|
+
|
|
5
|
+
```ruby
|
|
6
|
+
Whatsapp::Messages.send_video!(to: "+15551234567", id: "1234567890", caption: "Demo video")
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Fields
|
|
10
|
+
|
|
11
|
+
| Field | Required | Rules |
|
|
12
|
+
| --- | --- | --- |
|
|
13
|
+
| `to` | yes | The recipient's phone number |
|
|
14
|
+
| `id` | one of | A media ID from [`Media#upload`](../media/README.md) |
|
|
15
|
+
| `link` | one of | A publicly reachable HTTPS URL |
|
|
16
|
+
| `caption` | no | Max **1024** characters |
|
|
17
|
+
|
|
18
|
+
**Either `id` or `link` is required.** Videos are the kind where uploading once and
|
|
19
|
+
reusing the `id` pays off most — a `link` makes Meta re-download the whole file on
|
|
20
|
+
every single send.
|
|
21
|
+
|
|
22
|
+
## Serialized payload
|
|
23
|
+
|
|
24
|
+
```ruby
|
|
25
|
+
Whatsapp::Messages::Video.new(to: "+15551234567", id: "1234567890", caption: "Demo video").serialize
|
|
26
|
+
# => {
|
|
27
|
+
# messaging_product: "whatsapp", recipient_type: "individual", to: "+15551234567", type: "video",
|
|
28
|
+
# video: { id: "1234567890", caption: "Demo video" }
|
|
29
|
+
# }
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Validation errors
|
|
33
|
+
|
|
34
|
+
```ruby
|
|
35
|
+
Whatsapp::Messages.send_video!(to: "+15551234567")
|
|
36
|
+
# => ActiveModel::ValidationError: Either id or link must be present
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
**Meta docs:** <https://developers.facebook.com/documentation/business-messaging/whatsapp/messages/video-messages>
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# Managing Subscribed Apps
|
|
2
|
+
|
|
3
|
+
Before your app receives any [webhook](../webhooks/README.md) notifications for a
|
|
4
|
+
WhatsApp Business Account, it has to be **subscribed** to it.
|
|
5
|
+
`Whatsapp::SubscribedApp` wraps the `subscribed_apps` edge — the switch that turns
|
|
6
|
+
webhook delivery on or off.
|
|
7
|
+
|
|
8
|
+
> **Two halves of one thing.** This module makes Meta start sending;
|
|
9
|
+
> [`Whatsapp::Webhook`](../webhooks/README.md) deserializes what arrives. Neither is
|
|
10
|
+
> useful without the other.
|
|
11
|
+
|
|
12
|
+
Addresses your **WhatsApp Business Account** (`waba_id`, not `phone_id`) and needs the
|
|
13
|
+
`whatsapp_business_management` permission — same as
|
|
14
|
+
[template management](../message_templates/README.md).
|
|
15
|
+
|
|
16
|
+
```ruby
|
|
17
|
+
Whatsapp.configure do |config|
|
|
18
|
+
config.api_key = ENV.fetch("WHATSAPP_API_KEY")
|
|
19
|
+
config.waba_id = ENV.fetch("WHATSAPP_WABA_ID")
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
Whatsapp::SubscribedApp::Subscribe.call # start webhook delivery
|
|
23
|
+
Whatsapp::SubscribedApp::List.call.map(&:name) # => ["My App"]
|
|
24
|
+
Whatsapp::SubscribedApp::Unsubscribe.call # stop it
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
| Method | Request | Returns |
|
|
28
|
+
| --- | --- | --- |
|
|
29
|
+
| `List.call(client:, fields:)` | `GET /{waba_id}/subscribed_apps` | `Response::Collection` |
|
|
30
|
+
| `Subscribe.call(client:, override_callback_uri:, verify_token:)` | `POST /{waba_id}/subscribed_apps` | `Response::Subscription` |
|
|
31
|
+
| `Unsubscribe.call(client:)` | `DELETE /{waba_id}/subscribed_apps` | `Response::Unsubscription` |
|
|
32
|
+
|
|
33
|
+
Three independent actions, so three classes rather than one class with three methods —
|
|
34
|
+
unlike [templates](../message_templates/README.md), subscribing and listing share no
|
|
35
|
+
identity or validation rules that would justify combining them.
|
|
36
|
+
|
|
37
|
+
## Subscribing
|
|
38
|
+
|
|
39
|
+
```ruby
|
|
40
|
+
result = Whatsapp::SubscribedApp::Subscribe.call
|
|
41
|
+
|
|
42
|
+
result.success # => true
|
|
43
|
+
result.map(&:name) # => ["My App"] — every app now subscribed, Meta's own echo
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
`Response::Subscription` is `Enumerable` over the same `Response::App` objects `List`
|
|
47
|
+
returns, so you can check the result without a second request.
|
|
48
|
+
|
|
49
|
+
### Per-WABA callback overrides
|
|
50
|
+
|
|
51
|
+
Tech Providers routing several WABAs' notifications to different callback URLs pass an
|
|
52
|
+
override instead of relying on the single callback URL configured on the app itself:
|
|
53
|
+
|
|
54
|
+
```ruby
|
|
55
|
+
Whatsapp::SubscribedApp::Subscribe.call(
|
|
56
|
+
override_callback_uri: "https://example.com/webhooks/acme_corp",
|
|
57
|
+
verify_token: "a-per-account-secret"
|
|
58
|
+
)
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Both are optional and compacted out of the request body when absent. The
|
|
62
|
+
`verify_token` you pass here is the one Meta will send in the
|
|
63
|
+
[GET handshake](../webhooks/README.md#verification-the-get-handshake) for that WABA —
|
|
64
|
+
pass the matching value to `Verification.call(verify_token:)`.
|
|
65
|
+
|
|
66
|
+
> No client-side format check is applied to `override_callback_uri`. Meta enforces it
|
|
67
|
+
> server-side, and this gem validates only what Meta documents as a client-side rule.
|
|
68
|
+
|
|
69
|
+
## Listing
|
|
70
|
+
|
|
71
|
+
```ruby
|
|
72
|
+
apps = Whatsapp::SubscribedApp::List.call
|
|
73
|
+
|
|
74
|
+
apps.map(&:name) # Collection is Enumerable
|
|
75
|
+
apps.first.id # => "123456789"
|
|
76
|
+
apps.first.link # => "https://www.facebook.com/games/?app_id=..."
|
|
77
|
+
apps.first.override_callback_uri # => nil, or the per-WABA override
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Restrict what comes back with `fields:` — an Array or a String, comma-joined for you:
|
|
81
|
+
|
|
82
|
+
```ruby
|
|
83
|
+
Whatsapp::SubscribedApp::List.call(fields: %w[id name])
|
|
84
|
+
Whatsapp::SubscribedApp::List.call(fields: "id,name")
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
`Response::App` flattens Meta's `whatsapp_business_api_data` wrapper, so `id`, `name`,
|
|
88
|
+
and `link` sit directly on the object rather than a nested hash.
|
|
89
|
+
|
|
90
|
+
> No pagination — Meta documents none for this edge, and an account typically has very
|
|
91
|
+
> few subscribed apps.
|
|
92
|
+
|
|
93
|
+
## Unsubscribing
|
|
94
|
+
|
|
95
|
+
```ruby
|
|
96
|
+
Whatsapp::SubscribedApp::Unsubscribe.call.success # => true
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Stops **all** webhook deliveries for this WABA immediately.
|
|
100
|
+
|
|
101
|
+
## Response shapes
|
|
102
|
+
|
|
103
|
+
Three actions, three genuinely different shapes — hence three classes:
|
|
104
|
+
|
|
105
|
+
| Class | Holds | From |
|
|
106
|
+
| --- | --- | --- |
|
|
107
|
+
| `Response::Collection` | `data: [App]`, `Enumerable` | `List` |
|
|
108
|
+
| `Response::Subscription` | `success`, `data: [App]`, `Enumerable` | `Subscribe` |
|
|
109
|
+
| `Response::Unsubscription` | `success` | `Unsubscribe` |
|
|
110
|
+
|
|
111
|
+
`Response::App` — `id`, `name`, `link`, `override_callback_uri` — is composed by both
|
|
112
|
+
`Collection` and `Subscription` rather than duplicated.
|
|
113
|
+
|
|
114
|
+
`success` is a strict `response["success"] == true`, so it is never a truthy string
|
|
115
|
+
you have to second-guess.
|
|
116
|
+
|
|
117
|
+
## Errors
|
|
118
|
+
|
|
119
|
+
```ruby
|
|
120
|
+
Whatsapp::SubscribedApp::Subscribe.call
|
|
121
|
+
# => Whatsapp::SubscribedApp::Error: waba_id is required for subscribed apps;
|
|
122
|
+
# set it via Whatsapp.configure or Client.new(waba_id:)
|
|
123
|
+
|
|
124
|
+
# non-2xx response
|
|
125
|
+
# => Whatsapp::SubscribedApp::Error: Failed to subscribe app: 400 Bad Request - {...}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Every action raises `Whatsapp::SubscribedApp::Error` — never the generic
|
|
129
|
+
`Whatsapp::RequestError`. See [../errors.md](../errors.md).
|
|
130
|
+
|
|
131
|
+
## Targeting a different account
|
|
132
|
+
|
|
133
|
+
```ruby
|
|
134
|
+
Whatsapp::SubscribedApp::List.call(client: Whatsapp::Client.new(waba_id: "OTHER_WABA_ID"))
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
**Meta docs:** <https://developers.facebook.com/documentation/business-messaging/whatsapp/reference/whatsapp-business-account/subscribed-apps-api>
|