# Set up phone sign-in

Send sign-in codes by text message, WhatsApp or voice call through Twilio or your own webhook, set the limits, and see the code flow your users get.

Phone sign-in lets people sign in, or create an account, with a 6-digit code sent to their phone by text message, WhatsApp or a voice call. They type their number, then the code, and they are in; someone new also gives a name and an email. It suits apps whose users would rather type a phone number than an email address. Every message goes out through your own provider, which bills you for it.

> **Note:**

- **A way to send messages**: a Twilio account, or an HTTPS endpoint of your own that sends texts through any provider you already use.
- **A plan that includes phone sign-in codes.** Without one, the console refuses to save the SMS provider and the Phone method.
- **An email sender**, under **Messaging**, **Sender accounts**. People who sign up by phone also give an email address, and a code is emailed to confirm it.



## 1. Open the SMS tab

In the console's left rail, choose **Users**, then **Authentication** under **Access**, and open the **SMS** tab. Under **How codes are sent**, pick **Twilio** or **Webhook**. The card opens a guide for the one you pick.

![The SMS tab in the console: the SMS provider card, Not connected, with the countries warning, How codes are sent set to Twilio or Webhook, and the open guide How to get your Twilio details in seven steps](/images/sign-in-setup/phone/sms-provider-twilio-guide.webp)

## 2a. Connect Twilio

Twilio's console is at console.twilio.com. Copy these values from it into the matching BuildBase fields:

| Copy this from Twilio                    | Where it is in Twilio                           | Paste it into BuildBase                                  |
| ---------------------------------------- | ----------------------------------------------- | -------------------------------------------------------- |
| **Account SID** (starts with `AC`)       | The console's home page, under **Account Info** | **Account SID**                                          |
| **Auth Token**                           | Same place, hidden until you choose to show it  | **Auth token**                                           |
| **Service SID** (starts with `VA`)       | **Verify**, then **Services**. Recommended      | **Verify service SID**                                   |
| **Messaging Service SID** (starts `MG`)  | **Messaging**, then **Services**                | **Messaging service SID**, if you have no Verify service |
| One of your numbers, like `+14155550123` | **Phone Numbers**                               | **Or a number to send from**, if you have neither        |

Fill in the first two and one of the last three, then press **Connect**. BuildBase checks the account SID and auth token with Twilio before it saves them, and shows Twilio's own message if something is wrong. The auth token works like the password to your Twilio account: it is stored encrypted and never shown again.

![The Twilio fields filled with example values: Account SID AC1234567890abcdef1234567890abcdef, the Auth token hidden as dots, Verify service SID VA1234567890abcdef1234567890abcdef, and the Messaging service SID and number left empty](/images/sign-in-setup/phone/sms-twilio-fields.webp)

Nothing goes back the other way: you paste no BuildBase address into Twilio. A Verify service can send by text message, WhatsApp or voice call, and Twilio makes and checks the code. A Messaging service or a number sends text messages only.

## 2b. Or connect your own endpoint

Pick **Webhook** when you already send texts through another provider. Your developer builds an HTTPS endpoint that receives each code from BuildBase and sends it on.

1. Paste the endpoint's address into **Endpoint URL**, for example `https://api.acme.example/buildbase/sms`, and press **Connect**. BuildBase refuses an address it cannot reach over the internet.
2. **Copy your signing secret now** appears, once. Press **Copy** and give the secret to your developer, who keeps it in the endpoint's settings. **Rotate secret** makes a new one later; the old one stops working at once.

![Webhook chosen: the guide How to connect your own endpoint in five steps, and the Endpoint URL box with https://api.acme.example/buildbase/sms typed in](/images/sign-in-setup/phone/sms-webhook-guide.webp)

![After Connect: the box Copy your signing secret now, shown this once, with an example secret and its copy button](/images/sign-in-setup/phone/sms-signing-secret-once.webp)

For each code, BuildBase sends your endpoint a `POST` like this one, waits up to 5 seconds, and does not retry:

```json
{
  "to": "+14155550177",
  "code": "123456",
  "channel": "sms",
  "locale": "en-US",
  "purpose": "sign-up",
  "test": false
}
```

`purpose` is `sign-in`, `sign-up`, `phone-add`, `mfa` or `test`. Two headers sign it: `x-buildbase-timestamp` (Unix seconds) and `x-buildbase-signature`, `sha256=` followed by the HMAC-SHA256 of `<timestamp>.<raw body>` made with your signing secret. Check it on the raw body before you parse it; `verifyWebhookSignature` from the SDK does that in constant time and refuses a request older than 5 minutes:

```typescript
import { verifyWebhookSignature } from '@buildbase/sdk';

export async function POST(req: Request) {
  const body = await req.text();
  const valid = verifyWebhookSignature({
    body,
    signature: req.headers.get('x-buildbase-signature'),
    timestamp: req.headers.get('x-buildbase-timestamp'),
    secret: process.env.BUILDBASE_SMS_SECRET!,
  });
  if (!valid) return new Response('Invalid signature', { status: 401 });

  const { to, code, channel } = JSON.parse(body);
  const sent = await sendWithYourProvider({ to, code, channel });
  // A number your provider will not deliver to: answer 422 with a reason.
  if (!sent.ok) return Response.json({ reason: sent.reason }, { status: 422 });
  return Response.json({ sent: true });
}
```

Any `2xx` means sent. A `422` with `{ "reason": "..." }` means the number was refused: the sign-in page says so, and the delivery log keeps your reason. Anything else, or no answer in 5 seconds, is a failure.

## 3. Send a test

Once a provider is connected, the **Send a test** card appears. Type your own number with its country code, pick a **Channel** and press **Send a test code**. The code signs nothing in, but it is a real message on your provider's bill and counts toward the daily cap.

![The Send a test card after a test to +14155550123 by Text message: it says the test code was sent](/images/sign-in-setup/phone/sms-send-test.webp)

## 4. Set the limits

**Daily cap** is the most codes your app sends in a day (UTC), tests included, from 1 to 100,000; the default is 1,000. Past it, nothing is sent and the sign-in page says codes cannot be sent right now. **Warn at** sends the `auth.sms_budget_exceeded` event to your webhooks when the day's codes reach that share of the cap, and again when the cap stops a code, each at most once a day. **Sent today** counts against the cap.

![The limits: Daily cap 1000, Warn at 80 percent of the cap, and Sent today against the cap of 1,000](/images/sign-in-setup/phone/sms-limits.webp)

BuildBase also limits each number and each network address, on every install, before it looks the number up:

| Limit                                | Default |
| ------------------------------------ | ------- |
| One number, between two codes        | 30 s    |
| One number, an hour / a day          | 5 / 10  |
| One network address, an hour / a day | 20 / 50 |

> **Note:**
  **SMS fraud.** Some people run scripts that ask sign-in pages to text codes to
  expensive premium-rate numbers, and earn a share of what you pay (it is called
  SMS pumping). BuildBase sends to any valid number, because which countries can
  receive codes is set in your provider. Before you go live, allow only the
  countries your users are in: in Twilio, open **Verify** (or **Messaging**),
  then **Settings**, then **Geo permissions**. The daily cap limits how much a
  bad day can cost.


## 5. Choose the country and the channels

Under **Phone sign-in** on the same tab, pick the **Default country**: where the picker on the sign-in page starts, and how a number typed without a country code is read. Tick the **Ways to send the code**; with more than one, people choose on the sign-in page. Press **Save**.

![The Phone sign-in card: Default country United States (+1), Ways to send the code with Text message, WhatsApp and Voice call ticked, and a Save button](/images/sign-in-setup/phone/sms-phone-sign-in-card.webp)

## 6. Add the Phone method and attach it to your app

1. Open the **Methods** tab, press **Add auth method** and choose **Phone**. Keep the **Title** `Phone` and press **Create**.
2. Open the **Settings** tab, press **Edit** beside **Acme Analytics**, tick **Phone** under **Methods** and press **Update**. A method shows on the sign-in page only once it is ticked on the app.

![Phone chosen in the Add new method dialog: what it does, when to pick it, How to set up phone sign-in, the Title box filled with Phone, and Create](/images/sign-in-setup/phone/methods-phone-create.webp)

![The Acme Analytics client sheet, Methods: Email and password and Phone both ticked](/images/sign-in-setup/phone/client-methods-phone-ticked.webp)

## 7. Check the preview

The **Preview** on the **Sign-in page** tab draws your page's look with sample buttons, the same for every app, so it does not show the phone button. Press **Open the live page for Acme Analytics** above it: your real sign-in page opens in a new tab, with **Continue with phone**.

![The Preview on the Sign-in page tab: a sample card titled Sign in to Acme, and above it the button Open the live page for Acme Analytics](/images/sign-in-setup/phone/console-preview-live-button.webp)

![The live Acme sign-in page: email and password, then a Continue with phone button](/images/sign-in-setup/phone/hosted-sign-in-with-phone.webp)

## What people see

1. **Continue with phone** opens the phone form: the country, the number and, with more than one channel, how to send the code. The button names the channel, such as **Text me a code**.

![The phone form on the sign-in page: Country United States (+1), Phone number, Send the code by Text message, WhatsApp or Voice call, and the button Text me a code](/images/sign-in-setup/phone/hosted-phone-number.webp)

2. **Check your phone** asks for the 6-digit code. It lasts 10 minutes and allows 5 tries; **Resend code** works after 30 seconds, and a new code replaces the last.

![Check your phone: Enter the 6-digit code we texted to +1 415 555 0177, with the code from the text message typed in, a Continue button and Use a different number](/images/sign-in-setup/phone/hosted-phone-code.webp)

3. A number with no account goes to **Finish creating your account** for a name and an email, then confirms the email with a code, as after any sign-up. A number that has an account is signed in, after a [second step](/sign-in-setup/two-step) if you ask for one.

![Finish creating your account: the number is confirmed, and the form asks for a name, Sam Rivera, and an email, sam.rivera@acme.example](/images/sign-in-setup/phone/hosted-phone-signup.webp)

A number with an account and one without get the same answers until the code is right, so nobody can use the page to find out who has an account. An email that already belongs to another account is never given the phone. On a phone the form fills the screen:

![The phone form at phone width: the country picker, the number 415 555 0123, the three ways to send the code and Text me a code, filling the screen](/images/sign-in-setup/phone/hosted-phone-number-mobile.webp)

A number your provider refuses is said under the field:

![A number the provider refused: under the phone number the page says This number can't receive codes.](/images/sign-in-setup/phone/hosted-phone-refused.webp)

## The delivery log

The **Delivery log** at the bottom of the **SMS** tab lists every code of the last 30 days, newest first: the last four digits of the number, the channel, what it was for, the outcome and your provider's error code. The full number and the code are never kept. **Sent** means your provider accepted the message, not that it arrived; your provider's own logs have the rest.

![The Delivery log with three rows, newest first: a sign-up code to a number ending 0199 Refused with the provider error landline, a sign-up code to a number ending 0177 Sent, and a test code to 0123 Sent, all through the Webhook](/images/sign-in-setup/phone/sms-delivery-log.webp)

## In your app (SDK)

The SDK's **Security** screen has a **Phone number** section where a signed-in person adds a number, confirms it with a texted code, changes it or removes it. A verified number is also where [text message codes](/sign-in-setup/two-step) for two-step sign-in go. See [Account security screens](/sign-in-setup/account-security-screens#phone-number).

![The Phone number section once verified: a banner says +1 415 555 0142 is verified and on your account, and the number shows Verified with Change and Remove](/images/sign-in-setup/account-security-screens/sdk-phone-verified.webp)

## Check it works

- [ ] The **SMS** tab says **Connected**, and a test code reaches your phone.
- [ ] The **Delivery log** shows the test as **Sent**.
- [ ] **Phone** is listed on the **Methods** tab and ticked on your app.
- [ ] The live page shows **Continue with phone**, and a new number can sign up with the code it receives.
- [ ] Your provider allows only the countries your users are in.

## Things to watch out for

- **A Twilio trial account sends only to numbers you have verified in Twilio.** Upgrade the account before real people try to sign in.
- **WhatsApp needs a WhatsApp sender of your own.** Since 1 March 2024, Twilio Verify sends WhatsApp codes only from your own WhatsApp sender. Tick **WhatsApp** only once your Verify service has one. A Messaging Service or a single number sends text messages only.
- **Without Verify, the text is fixed and in English.** Through a Messaging Service or a number, the message always reads "Your verification code is" and the code. Verify sends Twilio's own wording in the person's language.
- **A United States number of your own needs A2P 10DLC registration.** Twilio says anyone texting United States numbers from an app over a regular 10-digit number must register, and charges extra carrier fees otherwise. Verification codes sent through Verify are the exception.
- **Allow only the countries your users are in.** Turn off every other country under **Geo permissions** in Twilio, and turn on Twilio's SMS fraud protection, before you go live.
- **Your own endpoint has 5 seconds and one try.** A slow or failing endpoint means the person sees "Codes can't be sent right now." BuildBase does not try again.
- **Test codes are real messages.** Each one is on your provider's bill and counts toward the daily cap.

## If something goes wrong

| What you or the person sees                                                                                              | Why                                                                 | What to do                                                             |
| ------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------- | ---------------------------------------------------------------------- |
| Twilio's own message when you press **Connect**, or "Twilio could not be reached. Try again."                            | Twilio refused the account SID, auth token or Verify service        | Copy the values again from **Account Info** and **Verify**             |
| "That URL cannot be reached from BuildBase. Use a public HTTPS address."                                                 | The endpoint address is private or not reachable                    | Use a public HTTPS address                                             |
| No **Continue with phone** on the sign-in page                                                                           | No provider connected, or **Phone** is not ticked on the app        | Connect a provider, tick **Phone** and press **Update**                |
| "Enter a valid phone number, with its country code if it is not local."                                                  | Not a valid number                                                  | Type the number with its country code                                  |
| "This number can't receive codes."                                                                                       | Your provider refused the number (a `422` from your endpoint)       | The delivery log shows the reason                                      |
| "Wait 30 seconds before asking for another code." (the number is the seconds left)                                       | A code went to this number moments ago                              | Wait, then press **Resend code**                                       |
| "Too many codes have been sent to this number. Try again later."                                                         | The number's or the address's hourly or daily limit                 | Wait                                                                   |
| "Codes can't be sent right now. Try another way to sign in."                                                             | The daily cap is reached, the provider failed, or none is connected | Check **Sent today**, the delivery log and your provider               |
| "That code did not match. Check the newest message and try again."                                                       | A wrong code, or an older one                                       | Type the code from the newest message                                  |
| "This code has expired. Ask for a new one."                                                                              | More than 10 minutes, or 5 wrong tries                              | Ask for a new code                                                     |
| "That email address cannot be used here. Use another, or sign in to the account it belongs to and add your phone there." | The email at sign-up already has an account                         | Sign in to that account and add the phone from its **Security** screen |

## Next steps

- [Two-step sign-in](/sign-in-setup/two-step) - text message codes as a second step.
- [Bot protection](/sign-in-setup/bot-protection) - a Turnstile check before codes are sent.
- [Phone sign-in reference](/authentication/phone) - the API routes, events and every refusal.
- [Set up sign-in, step by step](/sign-in-setup/overview) - every sign-in guide, and the order to set them up in.
