diff --git a/docs/config.md b/docs/config.md
index 353a9d03..d6f6e408 100644
--- a/docs/config.md
+++ b/docs/config.md
@@ -868,8 +868,8 @@ are the easiest), and then configure the following options:
 * `twilio-from-number` is the outgoing phone number you purchased, e.g. +18775132586 
 * `twilio-verify-service` is the Twilio Verify service SID, e.g. VA12345beefbeef67890beefbeef122586
 
-After you have configured phone calls, create a [tier](#tiers) with a call limit, and then assign it to a user.
-Users may then use the `X-Call` header to receive a phone call when publishing a message.
+After you have configured phone calls, create a [tier](#tiers) with a call limit (e.g. `ntfy tier create --call-limit=10 ...`),
+and then assign it to a user. Users may then use the `X-Call` header to receive a phone call when publishing a message.
 
 ## Rate limiting
 !!! info
diff --git a/docs/publish.md b/docs/publish.md
index 98f3e876..3cca6fc6 100644
--- a/docs/publish.md
+++ b/docs/publish.md
@@ -2702,16 +2702,26 @@ You can use ntfy to call a phone and **read the message out loud using text-to-s
 Similar to email notifications, this can be useful to blast-notify yourself on all possible channels, or to notify people that do not have 
 the ntfy app installed on their phone.
 
-**Phone numbers have to be previously verified** (via the web app), so this feature is **only available to authenticated users**. 
-To forward a message as a voice call, pass a phone number in the `X-Call` header (or its alias: `Call`), prefixed with a
-plus sign and the country code, e.g. `+12223334444`. You may also simply pass `yes` as a value to pick the first of your 
-verified phone numbers.
+**Phone numbers have to be previously verified** (via the [web app](https://ntfy.sh/account)), so this feature is 
+**only available to authenticated users** (no anonymous phone calls). To forward a message as a voice call, pass a phone
+number in the `X-Call` header (or its alias: `Call`), prefixed with a plus sign and the country code, e.g. `+12223334444`. 
+You may also simply pass `yes` as a value to pick the first of your verified phone numbers. 
+On ntfy.sh, this feature is only supported to [ntfy Pro](https://ntfy.sh/app) plans.
+
+<figure markdown>
+  ![e-mail publishing](static/img/web-phone-verify.png)
+  <figcaption>Phone number verification in the <a href="https://ntfy.sh/account">web app</a></figcaption>
+</figure>
+
+As of today, the text-to-speed voice used will only support English. If there is demand for other languages, we'll
+be happy to add support for that. Please [open an issue on GitHub](https://github.com/binwiederhier/ntfy/issues).
 
 !!! info
-    As of today, the text-to-speed voice used will only support English. If there is demand for other languages, we'll
-    be happy to add support for that. Please [open an issue on GitHub](https://github.com/binwiederhier/ntfy/issues).
+    You are responsible for the message content, and **you must abide by the [Twilio Acceptable Use Policy](https://www.twilio.com/en-us/legal/aup)**.
+    This particularly means that you must not use this feature to send unsolicited messages, or messages that are illegal or
+    violate the rights of others. Please read the policy for details. Failure to do so may result in your account being suspended or terminated.
 
-On ntfy.sh, this feature is only supported to [ntfy Pro](https://ntfy.sh/app) plans.
+Here's how you use it:
 
 === "Command line (curl)"
     ```
@@ -3431,17 +3441,18 @@ There are a few limitations to the API to prevent abuse and to keep the server h
 are configurable via the server side [rate limiting settings](config.md#rate-limiting). Most of these limits you won't run into,
 but just in case, let's list them all:
 
-| Limit                           | Description                                                                                                                                                                                                             |
-|---------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
-| **Message length**              | Each message can be up to 4,096 bytes long. Longer messages are treated as [attachments](#attachments).                                                                                                                 |
-| **Requests**                    | By default, the server is configured to allow 60 requests per visitor at once, and then refills the your allowed requests bucket at a rate of one request per 5 seconds.                                                |
-| **Daily messages**              | By default, the number of messages is governed by the request limits. This can be overridden. On ntfy.sh, the daily message limit is 250.                                                                               |
-| **E-mails**                     | By default, the server is configured to allow sending 16 e-mails per visitor at once, and then refills the your allowed e-mail bucket at a rate of one per hour. On ntfy.sh, the daily limit is 5.                      |
-| **Subscription limit**          | By default, the server allows each visitor to keep 30 connections to the server open.                                                                                                                                   |
-| **Attachment size limit**       | By default, the server allows attachments up to 15 MB in size, up to 100 MB in total per visitor and up to 5 GB across all visitors. On ntfy.sh, the attachment size limit is 2 MB, and the per-visitor total is 20 MB. |
-| **Attachment expiry**           | By default, the server deletes attachments after 3 hours and thereby frees up space from the total visitor attachment limit.                                                                                            |
-| **Attachment bandwidth**        | By default, the server allows 500 MB of GET/PUT/POST traffic for attachments per visitor in a 24 hour period. Traffic exceeding that is rejected. On ntfy.sh, the daily bandwidth limit is 200 MB.                      |
-| **Total number of topics**      | By default, the server is configured to allow 15,000 topics. The ntfy.sh server has higher limits though.                                                                                                               |
+| Limit                      | Description                                                                                                                                                                                                             |
+|----------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| **Message length**         | Each message can be up to 4,096 bytes long. Longer messages are treated as [attachments](#attachments).                                                                                                                 |
+| **Requests**               | By default, the server is configured to allow 60 requests per visitor at once, and then refills the your allowed requests bucket at a rate of one request per 5 seconds.                                                |
+| **Daily messages**         | By default, the number of messages is governed by the request limits. This can be overridden. On ntfy.sh, the daily message limit is 250.                                                                               |
+| **E-mails**                | By default, the server is configured to allow sending 16 e-mails per visitor at once, and then refills the your allowed e-mail bucket at a rate of one per hour. On ntfy.sh, the daily limit is 5.                      |
+| **Phone calls**            | By default, the server does not allow any phone calls, except for users with a tier that has a call limit.                                                                                                              |
+| **Subscription limit**     | By default, the server allows each visitor to keep 30 connections to the server open.                                                                                                                                   |
+| **Attachment size limit**  | By default, the server allows attachments up to 15 MB in size, up to 100 MB in total per visitor and up to 5 GB across all visitors. On ntfy.sh, the attachment size limit is 2 MB, and the per-visitor total is 20 MB. |
+| **Attachment expiry**      | By default, the server deletes attachments after 3 hours and thereby frees up space from the total visitor attachment limit.                                                                                            |
+| **Attachment bandwidth**   | By default, the server allows 500 MB of GET/PUT/POST traffic for attachments per visitor in a 24 hour period. Traffic exceeding that is rejected. On ntfy.sh, the daily bandwidth limit is 200 MB.                      |
+| **Total number of topics** | By default, the server is configured to allow 15,000 topics. The ntfy.sh server has higher limits though.                                                                                                               |
 
 These limits can be changed on a per-user basis using [tiers](config.md#tiers). If [payments](config.md#payments) are enabled, a user tier can be changed by purchasing
 a higher tier. ntfy.sh offers multiple paid tiers, which allows for much hier limits than the ones listed above. 
@@ -3470,6 +3481,7 @@ table in their canonical form.
 | `X-Icon`        | `Icon`                                     | URL to use as notification [icon](#icons)                                                     |
 | `X-Filename`    | `Filename`, `file`, `f`                    | Optional [attachment](#attachments) filename, as it appears in the client                     |
 | `X-Email`       | `X-E-Mail`, `Email`, `E-Mail`, `mail`, `e` | E-mail address for [e-mail notifications](#e-mail-notifications)                              |
+| `X-Call`        | `Call`                                     | Phone number for [phone calls](#phone-calls)                                                  |
 | `X-Cache`       | `Cache`                                    | Allows disabling [message caching](#message-caching)                                          |
 | `X-Firebase`    | `Firebase`                                 | Allows disabling [sending to Firebase](#disable-firebase)                                     |
 | `X-UnifiedPush` | `UnifiedPush`, `up`                        | [UnifiedPush](#unifiedpush) publish option, only to be used by UnifiedPush apps               |
diff --git a/docs/static/img/web-phone-verify.png b/docs/static/img/web-phone-verify.png
new file mode 100644
index 00000000..335aeef1
Binary files /dev/null and b/docs/static/img/web-phone-verify.png differ
diff --git a/go.sum b/go.sum
index ccb1ae4d..ff2d580f 100644
--- a/go.sum
+++ b/go.sum
@@ -1,23 +1,15 @@
 cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
 cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
-cloud.google.com/go v0.110.1 h1:oDJ19Fu9TX9Xs06iyCw4yifSqZ7JQ8BeuVHcTmWQlOA=
-cloud.google.com/go v0.110.1/go.mod h1:uc+V/WjzxQ7vpkxfJhgW4Q4axWXyfAerpQOuSNDZyFw=
 cloud.google.com/go v0.110.2 h1:sdFPBr6xG9/wkBbfhmUz/JmZC7X6LavQgcrVINrKiVA=
 cloud.google.com/go v0.110.2/go.mod h1:k04UEeEtb6ZBRTv3dZz4CeJC3jKGxyhl0sAiVVquxiw=
-cloud.google.com/go/compute v1.19.1 h1:am86mquDUgjGNWxiGn+5PGLbmgiWXlE/yNWpIpNvuXY=
-cloud.google.com/go/compute v1.19.1/go.mod h1:6ylj3a05WF8leseCdIf77NK0g1ey+nj5IKd5/kvShxE=
 cloud.google.com/go/compute v1.19.2 h1:GbJtPo8OKVHbVep8jvM57KidbYHxeE68LOVqouNLrDY=
 cloud.google.com/go/compute v1.19.2/go.mod h1:5f5a+iC1IriXYauaQ0EyQmEAEq9CGRnV5xJSQSlTV08=
 cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
 cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
 cloud.google.com/go/firestore v1.9.0 h1:IBlRyxgGySXu5VuW0RgGFlTtLukSnNkpDiEOMkQkmpA=
 cloud.google.com/go/firestore v1.9.0/go.mod h1:HMkjKHNTtRyZNiMzu7YAsLr9K3X2udY2AMwDaMEQiiE=
-cloud.google.com/go/iam v1.0.0 h1:hlQJMovyJJwYjZcTohUH4o1L8Z8kYz+E+W/zktiLCBc=
-cloud.google.com/go/iam v1.0.0/go.mod h1:ikbQ4f1r91wTmBmmOtBCOtuEOei6taatNXytzB7Cxew=
 cloud.google.com/go/iam v1.0.1 h1:lyeCAU6jpnVNrE9zGQkTl3WgNgK/X+uWwaw0kynZJMU=
 cloud.google.com/go/iam v1.0.1/go.mod h1:yR3tmSL8BcZB4bxByRv2jkSIahVmCtfKZwLYGBalRE8=
-cloud.google.com/go/longrunning v0.4.1 h1:v+yFJOfKC3yZdY6ZUI933pIYdhyhV8S3NpWrXWmg7jM=
-cloud.google.com/go/longrunning v0.4.1/go.mod h1:4iWDqhBZ70CvZ6BfETbvam3T8FMvLK+eFj0E6AaRQTo=
 cloud.google.com/go/longrunning v0.4.2 h1:WDKiiNXFTaQ6qz/G8FCOkuY9kJmOJGY67wPUC1M2RbE=
 cloud.google.com/go/longrunning v0.4.2/go.mod h1:OHrnaYyLUV6oqwh0xiS7e5sLQhP1m0QU9R+WhGDMgIQ=
 cloud.google.com/go/storage v1.30.1 h1:uOdMxAs8HExqBlnLtnQyP0YkvbiDpdGShGKtx6U/oNM=
@@ -147,8 +139,6 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
 github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
 github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
 github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
-github.com/stripe/stripe-go/v74 v74.17.0 h1:qVWSzmADr6gudznuAcPjB9ewzgxfyIhBCkyTbkxJcCw=
-github.com/stripe/stripe-go/v74 v74.17.0/go.mod h1:f9L6LvaXa35ja7eyvP6GQswoaIPaBRvGAimAO+udbBw=
 github.com/stripe/stripe-go/v74 v74.18.0 h1:ImSIoaVkTUozHxa21AhwHYBjwc8fVSJJJB1Q7oaXzIw=
 github.com/stripe/stripe-go/v74 v74.18.0/go.mod h1:f9L6LvaXa35ja7eyvP6GQswoaIPaBRvGAimAO+udbBw=
 github.com/urfave/cli/v2 v2.25.3 h1:VJkt6wvEBOoSjPFQvOkv6iWIrsJyCrKGtCtxXWwmGeY=
@@ -163,8 +153,6 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
-golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ=
-golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
 golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
 golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -186,14 +174,10 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
 golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
-golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
-golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
 golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
 golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
-golang.org/x/oauth2 v0.7.0 h1:qe6s0zUXlPX80/dITx3440hWZ7GwMwgDDyrSGTPJG/g=
-golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4=
 golang.org/x/oauth2 v0.8.0 h1:6dkIjl3j3LtZ/O3sTgZTMsLKSftL/B8Zgq4huOIIUu8=
 golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -241,8 +225,6 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk=
 golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
-google.golang.org/api v0.121.0 h1:8Oopoo8Vavxx6gt+sgs8s8/X60WBAtKQq6JqnkF+xow=
-google.golang.org/api v0.121.0/go.mod h1:gcitW0lvnyWjSp9nKxAbdHKIZ6vF4aajGueeslZOyms=
 google.golang.org/api v0.122.0 h1:zDobeejm3E7pEG1mNHvdxvjs5XJoCMzyNH+CmwL94Es=
 google.golang.org/api v0.122.0/go.mod h1:gcitW0lvnyWjSp9nKxAbdHKIZ6vF4aajGueeslZOyms=
 google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
diff --git a/server/errors.go b/server/errors.go
index a42641b4..b67558db 100644
--- a/server/errors.go
+++ b/server/errors.go
@@ -112,6 +112,7 @@ var (
 	errHTTPBadRequestPhoneNumberInvalid              = &errHTTP{40033, http.StatusBadRequest, "invalid request: phone number invalid", "https://ntfy.sh/docs/publish/#phone-calls", nil}
 	errHTTPBadRequestPhoneNumberNotVerified          = &errHTTP{40034, http.StatusBadRequest, "invalid request: phone number not verified, or no matching verified numbers found", "https://ntfy.sh/docs/publish/#phone-calls", nil}
 	errHTTPBadRequestAnonymousCallsNotAllowed        = &errHTTP{40035, http.StatusBadRequest, "invalid request: anonymous phone calls are not allowed", "https://ntfy.sh/docs/publish/#phone-calls", nil}
+	errHTTPBadRequestPhoneNumberVerifyChannelInvalid = &errHTTP{40036, http.StatusBadRequest, "invalid request: verification channel must be 'sms' or 'call'", "https://ntfy.sh/docs/publish/#phone-calls", nil}
 	errHTTPNotFound                                  = &errHTTP{40401, http.StatusNotFound, "page not found", "", nil}
 	errHTTPUnauthorized                              = &errHTTP{40101, http.StatusUnauthorized, "unauthorized", "https://ntfy.sh/docs/publish/#authentication", nil}
 	errHTTPForbidden                                 = &errHTTP{40301, http.StatusForbidden, "forbidden", "https://ntfy.sh/docs/publish/#authentication", nil}
diff --git a/server/server_account.go b/server/server_account.go
index 2330eab8..b9997ef3 100644
--- a/server/server_account.go
+++ b/server/server_account.go
@@ -523,12 +523,13 @@ func (s *Server) maybeRemoveMessagesAndExcessReservations(r *http.Request, v *vi
 
 func (s *Server) handleAccountPhoneNumberVerify(w http.ResponseWriter, r *http.Request, v *visitor) error {
 	u := v.User()
-	req, err := readJSONWithLimit[apiAccountPhoneNumberRequest](r.Body, jsonBodyBytesLimit, false)
+	req, err := readJSONWithLimit[apiAccountPhoneNumberVerifyRequest](r.Body, jsonBodyBytesLimit, false)
 	if err != nil {
 		return err
-	}
-	if !phoneNumberRegex.MatchString(req.Number) {
+	} else if !phoneNumberRegex.MatchString(req.Number) {
 		return errHTTPBadRequestPhoneNumberInvalid
+	} else if req.Channel != "sms" && req.Channel != "call" {
+		return errHTTPBadRequestPhoneNumberVerifyChannelInvalid
 	}
 	// Check user is allowed to add phone numbers
 	if u == nil || (u.IsUser() && u.Tier == nil) {
@@ -545,7 +546,7 @@ func (s *Server) handleAccountPhoneNumberVerify(w http.ResponseWriter, r *http.R
 	}
 	// Actually add the unverified number, and send verification
 	logvr(v, r).Tag(tagAccount).Field("phone_number", req.Number).Debug("Sending phone number verification")
-	if err := s.verifyPhoneNumber(v, r, req.Number); err != nil {
+	if err := s.verifyPhoneNumber(v, r, req.Number, req.Channel); err != nil {
 		return err
 	}
 	return s.writeJSON(w, newSuccessResponse())
@@ -553,7 +554,7 @@ func (s *Server) handleAccountPhoneNumberVerify(w http.ResponseWriter, r *http.R
 
 func (s *Server) handleAccountPhoneNumberAdd(w http.ResponseWriter, r *http.Request, v *visitor) error {
 	u := v.User()
-	req, err := readJSONWithLimit[apiAccountPhoneNumberRequest](r.Body, jsonBodyBytesLimit, false)
+	req, err := readJSONWithLimit[apiAccountPhoneNumberAddRequest](r.Body, jsonBodyBytesLimit, false)
 	if err != nil {
 		return err
 	}
@@ -572,7 +573,7 @@ func (s *Server) handleAccountPhoneNumberAdd(w http.ResponseWriter, r *http.Requ
 
 func (s *Server) handleAccountPhoneNumberDelete(w http.ResponseWriter, r *http.Request, v *visitor) error {
 	u := v.User()
-	req, err := readJSONWithLimit[apiAccountPhoneNumberRequest](r.Body, jsonBodyBytesLimit, false)
+	req, err := readJSONWithLimit[apiAccountPhoneNumberAddRequest](r.Body, jsonBodyBytesLimit, false)
 	if err != nil {
 		return err
 	}
diff --git a/server/server_twilio.go b/server/server_twilio.go
index f8067490..11f58aa7 100644
--- a/server/server_twilio.go
+++ b/server/server_twilio.go
@@ -18,13 +18,14 @@ const (
 <Response>
 	<Pause length="1"/>
 	<Say loop="3">
-		You have a notification from notify on topic %s. Message:
+		You have a message from notify on topic %s. Message:
 		<break time="1s"/>
 		%s
 		<break time="1s"/>
-		End message.
+		End of message.
 		<break time="1s"/>
-		This message was sent by user %s. It will be repeated up to three times.
+		This message was sent by user %s. It will be repeated three times.
+		To unsubscribe from calls like this, remove your phone number in the notify web app.
 		<break time="3s"/>
 	</Say>
 	<Say>Goodbye.</Say>
@@ -97,11 +98,11 @@ func (s *Server) callPhoneInternal(data url.Values) (string, error) {
 	return string(response), nil
 }
 
-func (s *Server) verifyPhoneNumber(v *visitor, r *http.Request, phoneNumber string) error {
-	ev := logvr(v, r).Tag(tagTwilio).Field("twilio_to", phoneNumber).Debug("Sending phone verification")
+func (s *Server) verifyPhoneNumber(v *visitor, r *http.Request, phoneNumber, channel string) error {
+	ev := logvr(v, r).Tag(tagTwilio).Field("twilio_to", phoneNumber).Field("twilio_channel", channel).Debug("Sending phone verification")
 	data := url.Values{}
 	data.Set("To", phoneNumber)
-	data.Set("Channel", "sms")
+	data.Set("Channel", channel)
 	requestURL := fmt.Sprintf("%s/v2/Services/%s/Verifications", s.config.TwilioVerifyBaseURL, s.config.TwilioVerifyService)
 	req, err := http.NewRequest(http.MethodPost, requestURL, strings.NewReader(data.Encode()))
 	if err != nil {
diff --git a/server/types.go b/server/types.go
index a1d18926..3b733678 100644
--- a/server/types.go
+++ b/server/types.go
@@ -311,9 +311,14 @@ type apiAccountTokenResponse struct {
 	Expires    int64  `json:"expires,omitempty"` // Unix timestamp
 }
 
-type apiAccountPhoneNumberRequest struct {
+type apiAccountPhoneNumberVerifyRequest struct {
+	Number  string `json:"number"`
+	Channel string `json:"channel"`
+}
+
+type apiAccountPhoneNumberAddRequest struct {
 	Number string `json:"number"`
-	Code   string `json:"code,omitempty"` // Only supplied in "verify" call
+	Code   string `json:"code,omitempty"`
 }
 
 type apiAccountTier struct {
diff --git a/web/public/static/langs/en.json b/web/public/static/langs/en.json
index f2120e5f..588a1f9f 100644
--- a/web/public/static/langs/en.json
+++ b/web/public/static/langs/en.json
@@ -188,17 +188,20 @@
   "account_basics_password_dialog_button_submit": "Change password",
   "account_basics_password_dialog_current_password_incorrect": "Password incorrect",
   "account_basics_phone_numbers_title": "Phone numbers",
-  "account_basics_phone_numbers_dialog_description": "To use the call notification feature, you need to add and verify at least one phone number. Adding it will send a verification SMS to your phone.",
+  "account_basics_phone_numbers_dialog_description": "To use the call notification feature, you need to add and verify at least one phone number. Verification can be done via SMS or a phone call.",
   "account_basics_phone_numbers_description": "For phone call notifications",
   "account_basics_phone_numbers_no_phone_numbers_yet": "No phone numbers yet",
   "account_basics_phone_numbers_copied_to_clipboard": "Phone number copied to clipboard",
   "account_basics_phone_numbers_dialog_title": "Add phone number",
   "account_basics_phone_numbers_dialog_number_label": "Phone number",
   "account_basics_phone_numbers_dialog_number_placeholder": "e.g. +1222333444",
-  "account_basics_phone_numbers_dialog_send_verification_button": "Send verification",
+  "account_basics_phone_numbers_dialog_verify_button_sms": "Send SMS",
+  "account_basics_phone_numbers_dialog_verify_button_call": "Call me",
   "account_basics_phone_numbers_dialog_code_label": "Verification code",
   "account_basics_phone_numbers_dialog_code_placeholder": "e.g. 123456",
   "account_basics_phone_numbers_dialog_check_verification_button": "Confirm code",
+  "account_basics_phone_numbers_dialog_channel_sms": "SMS",
+  "account_basics_phone_numbers_dialog_channel_call": "Call",
   "account_usage_title": "Usage",
   "account_usage_of_limit": "of {{limit}}",
   "account_usage_unlimited": "Unlimited",
diff --git a/web/src/app/AccountApi.js b/web/src/app/AccountApi.js
index b5bfcd29..8908f306 100644
--- a/web/src/app/AccountApi.js
+++ b/web/src/app/AccountApi.js
@@ -299,14 +299,15 @@ class AccountApi {
         return await response.json(); // May throw SyntaxError
     }
 
-    async verifyPhoneNumber(phoneNumber) {
+    async verifyPhoneNumber(phoneNumber, channel) {
         const url = accountPhoneVerifyUrl(config.base_url);
         console.log(`[AccountApi] Sending phone verification ${url}`);
         await fetchOrThrow(url, {
             method: "PUT",
             headers: withBearerAuth({}, session.token()),
             body: JSON.stringify({
-                number: phoneNumber
+                number: phoneNumber,
+                channel: channel
             })
         });
     }
diff --git a/web/src/components/Account.js b/web/src/components/Account.js
index b4a378e6..b480ea6b 100644
--- a/web/src/components/Account.js
+++ b/web/src/components/Account.js
@@ -1,13 +1,13 @@
 import * as React from 'react';
 import {useContext, useState} from 'react';
 import {
-    Alert,
+    Alert, ButtonGroup,
     CardActions,
     CardContent, Chip,
-    FormControl,
+    FormControl, FormControlLabel, InputLabel,
     LinearProgress,
     Link,
-    Portal,
+    Portal, Radio, RadioGroup,
     Select,
     Snackbar,
     Stack,
@@ -47,12 +47,14 @@ import {AccountContext} from "./App";
 import DialogFooter from "./DialogFooter";
 import {Paragraph} from "./styles";
 import CloseIcon from "@mui/icons-material/Close";
-import {ContentCopy, Public} from "@mui/icons-material";
+import {Check, ContentCopy, DeleteForever, Public} from "@mui/icons-material";
 import MenuItem from "@mui/material/MenuItem";
 import DialogContentText from "@mui/material/DialogContentText";
 import {IncorrectPasswordError, UnauthorizedError} from "../app/errors";
 import {ProChip} from "./SubscriptionPopup";
 import AddIcon from "@mui/icons-material/Add";
+import ListItemIcon from "@mui/material/ListItemIcon";
+import ListItemText from "@mui/material/ListItemText";
 
 const Account = () => {
     if (!session.exists()) {
@@ -408,6 +410,7 @@ const AddPhoneNumberDialog = (props) => {
     const { t } = useTranslation();
     const [error, setError] = useState("");
     const [phoneNumber, setPhoneNumber] = useState("");
+    const [channel, setChannel] = useState("sms");
     const [code, setCode] = useState("");
     const [sending, setSending] = useState(false);
     const [verificationCodeSent, setVerificationCodeSent] = useState(false);
@@ -432,7 +435,7 @@ const AddPhoneNumberDialog = (props) => {
     const verifyPhone = async () => {
         try {
             setSending(true);
-            await accountApi.verifyPhoneNumber(phoneNumber);
+            await accountApi.verifyPhoneNumber(phoneNumber, channel);
             setVerificationCodeSent(true);
         } catch (e) {
             console.log(`[Account] Error sending verification`, e);
@@ -471,18 +474,26 @@ const AddPhoneNumberDialog = (props) => {
                     {t("account_basics_phone_numbers_dialog_description")}
                 </DialogContentText>
                 {!verificationCodeSent &&
-                    <TextField
-                        margin="dense"
-                        label={t("account_basics_phone_numbers_dialog_number_label")}
-                        aria-label={t("account_basics_phone_numbers_dialog_number_label")}
-                        placeholder={t("account_basics_phone_numbers_dialog_number_placeholder")}
-                        type="tel"
-                        value={phoneNumber}
-                        onChange={ev => setPhoneNumber(ev.target.value)}
-                        fullWidth
-                        inputProps={{ inputMode: 'tel', pattern: '\+[0-9]*' }}
-                        variant="standard"
-                    />
+                    <div style={{display: "flex"}}>
+                        <TextField
+                            margin="dense"
+                            label={t("account_basics_phone_numbers_dialog_number_label")}
+                            aria-label={t("account_basics_phone_numbers_dialog_number_label")}
+                            placeholder={t("account_basics_phone_numbers_dialog_number_placeholder")}
+                            type="tel"
+                            value={phoneNumber}
+                            onChange={ev => setPhoneNumber(ev.target.value)}
+                            inputProps={{ inputMode: 'tel', pattern: '\+[0-9]*' }}
+                            variant="standard"
+                            sx={{ flexGrow: 1 }}
+                        />
+                        <FormControl sx={{ flexWrap: "nowrap" }}>
+                            <RadioGroup row sx={{ flexGrow: 1, marginTop: "8px", marginLeft: "5px" }}>
+                                <FormControlLabel value="sms" control={<Radio checked={channel === "sms"} onChange={(e) => setChannel(e.target.value)} />} label={t("account_basics_phone_numbers_dialog_channel_sms")} />
+                                <FormControlLabel value="call" control={<Radio checked={channel === "call"} onChange={(e) => setChannel(e.target.value)} />} label={t("account_basics_phone_numbers_dialog_channel_call")} sx={{ marginRight: 0 }} />
+                            </RadioGroup>
+                        </FormControl>
+                    </div>
                 }
                 {verificationCodeSent &&
                     <TextField
@@ -502,7 +513,9 @@ const AddPhoneNumberDialog = (props) => {
             <DialogFooter status={error}>
                 <Button onClick={handleCancel}>{verificationCodeSent ? t("common_back") : t("common_cancel")}</Button>
                 <Button onClick={handleDialogSubmit} disabled={sending || !/^\+\d+$/.test(phoneNumber)}>
-                    {verificationCodeSent ?t("account_basics_phone_numbers_dialog_check_verification_button")  : t("account_basics_phone_numbers_dialog_send_verification_button")}
+                    {!verificationCodeSent && channel === "sms" && t("account_basics_phone_numbers_dialog_verify_button_sms")}
+                    {!verificationCodeSent && channel === "call" && t("account_basics_phone_numbers_dialog_verify_button_call")}
+                    {verificationCodeSent && t("account_basics_phone_numbers_dialog_check_verification_button")}
                 </Button>
             </DialogFooter>
         </Dialog>