> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tawked.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Supabase phone sign-in

> Send the phone sign-in codes of your Supabase project to Saudi numbers through Tawked Verify, with Supabase's Send SMS hook and no code of your own.

Tawked Verify is an OTP verification API for Saudi numbers. As your Supabase Auth Send SMS hook, it delivers the codes Supabase makes, by SMS to Saudi mobile numbers, naming your approved application; `signInWithOtp` and `verifyOtp` stay as they are.

Your app signs people in with **Supabase Auth** by phone. Supabase makes the code and checks it; Tawked delivers it. You point Supabase's **Send SMS hook** at Tawked, and every code Supabase makes goes out as a Tawked Verify SMS: to Saudi mobile numbers, under Tawked's sender ID, with the text naming your reviewed application. The product page, in [English](https://tawked.com/en/supabase) and [Arabic](https://tawked.com/ar/supabase), has the short version.

| It does | It does not |
| - | - |
| Delivers every code Supabase makes, by SMS, to Saudi mobiles | Check codes: Supabase's `verifyOtp` does |
| Follows your application's review: owner's phone only until approved | Send to numbers outside Saudi Arabia, or by WhatsApp |
| Charges each code at the Verify price, from your balance | Need code in your app, or a Tawked API key in Supabase |

Your app's code stays as it is:

```javascript theme={"dark"}
// Supabase makes a code and hands it to the hook: Tawked sends it.
await supabase.auth.signInWithOtp({ phone: '+966512345678' })

// Supabase checks the code the person typed.
const { data, error } = await supabase.auth.verifyOtp({
  phone: '+966512345678',
  token: '123456',
  type: 'sms',
})
```

| | The Verify API | The Supabase hook |
| - | - | - |
| Makes the code | Tawked | Supabase |
| Sends it | Tawked | Tawked |
| Checks it | `POST /v1/verify/check` | Supabase (`verifyOtp`) |
| Review, the application's name in the text, Tawked's sender ID, price, wallet, caps | Tawked | Tawked |

Use the hook when your app already signs people in with Supabase Auth. If your own server starts and checks codes, use the [Verify API](/quickstart) instead; both can live side by side on one application.

## Before you start

* A Tawked application with Tawked Verify. The fastest start is the Supabase sign-up, [tawked.com/en/signup?product=verify\&via=supabase](https://tawked.com/en/signup?product=verify\&via=supabase): it makes the application with Verify and opens its Supabase page.
* A Supabase project with phone sign-in, and access to its **Authentication** settings.
* To create the connection: the owner, or an admin or developer whose access covers the whole application, since it makes keys.

## Set it up

<Steps>
  <Step title="Create the connection in Tawked">
    In the console, open the application, then **Integrations**, then **Supabase**, and choose **Create the connection**. The page shows a **Hook URL** for this application alone and a **Secret**. The application needs Verify, and whoever manages the application's API keys (the owner, or an admin or developer whose access covers the whole application) creates the connection and sees the Secret.
  </Step>

  <Step title="Add the hook in Supabase">
    In your Supabase project open **Authentication**, then **Auth Hooks**, choose **Add hook**, then **Send SMS hook**. Choose **HTTPS**, paste the Hook URL and the Secret, and save. If Supabase made a secret for you instead, paste that one into Tawked under **Have a secret from Supabase?**: either way, both sides must hold the same one.
  </Step>

  <Step title="Turn on phone sign-in">
    In **Authentication › Sign In / Providers**, turn on **Phone**. Then sign in to your app with your own phone. The Supabase page in Tawked updates by itself when the first request arrives and when the first code goes out.
  </Step>
</Steps>

The Hook URL looks like `https://tawked.com/webhooks/supabase/{connection}`, and the Secret starts with `v1,whsec_` followed by Base64. Treat the Secret like an API key: keep it in Supabase's hook settings or your secret manager, never in your app's code.

### The local Supabase CLI

For a project run with the Supabase CLI, the same hook goes in `supabase/config.toml`:

```toml theme={"dark"}
[auth.sms]
enable_signup = true

[auth.hook.send_sms]
enabled = true
uri = "https://tawked.com/webhooks/supabase/{connection}"
secrets = "env(TAWKED_SUPABASE_HOOK_SECRET)"
```

Put the Secret in the environment variable (`TAWKED_SUPABASE_HOOK_SECRET=v1,whsec_...`), then restart the local stack with `supabase stop` and `supabase start`.

## Configure

On the application's **Integrations → Supabase** page:

| Setting | What it does | Default |
| - | - | - |
| **Message language** | The SMS goes out in Arabic or English, one language per connection. | Arabic |
| **Send a test request** | Tawked signs a request shaped like Supabase's with the stored Secret and runs it through the hook, as the test key, to the account owner's phone. It shows the request and the answer. It is a real test send: charged like any code, and counted toward the 10 test sends per application while the application is in review. | |
| **Latest codes** | The last codes the connection sent. Every code is also in **Verify → Verifications**, with the Supabase user's id as its reference. | |
| **New secret** | Makes another Secret. The connection stops until you put the new one in Supabase. | |
| **Disconnect Supabase** | Revokes the connection's keys. Codes already sent stay in the Verify log. | |

## Review and test mode

Every application is reviewed before its codes reach the public, and the hook follows the same rule as a [test key](/sandbox):

* **In review**: codes reach the phone the account owner verified at signup, and no other number. Each one carries the test stamp and counts toward the same 10 sends per application as any sandbox send. Any other number is refused with "is in review".
* **Approved**: codes reach every Saudi mobile number.
* **Rejected, suspended or archived**: nothing is sent, and every request is refused.

The connection sends with keys of its own, named **Supabase**, made at its first send in each mode and limited to sending codes. They are listed on **Application settings → API keys**. Revoking one there stops the connection: Tawked never makes a replacement behind your back, so disconnect and connect again from **Integrations**.

Each code is charged at the [Verify price](https://tawked.com/en/pricing) from the account's prepaid balance, like any code sent through the API.

<Note>
  Supabase checks the code, so Tawked never learns whether the person typed it. These codes are delivery only: the Verify API answers `404 not_found` for them, they never expire into a `verification.expired` webhook, and the Verify log shows them as sent, with "Supabase checks the code". Never read their state as the sign-in result: `verifyOtp` is the answer.
</Note>

## What Supabase sends

Supabase calls the Hook URL with a `POST` signed by [Standard Webhooks](https://www.standardwebhooks.com): the headers `webhook-id`, `webhook-timestamp` and `webhook-signature` (`v1,` and a Base64 HMAC-SHA256 of `{webhook-id}.{webhook-timestamp}.{body}`, keyed by the Secret). Tawked refuses a signature it cannot match, and a timestamp more than five minutes away from its clock.

```json theme={"dark"}
{
  "metadata": {
    "uuid": "8b6f0f9e-2a51-4d43-9c1e-7f2d3a4b5c6d",
    "name": "send-sms",
    "ip_address": "203.0.113.7"
  },
  "user": {
    "id": "3f2a9c1e-5b7d-4e8f-a1b2-c3d4e5f6a7b8",
    "phone": "966512345678"
  },
  "sms": {
    "otp": "123456",
    "phone": "966512345678"
  }
}
```

Tawked reads:

* `sms.phone`, where the code goes (the new number on a phone change), or `user.phone` when `sms.phone` is missing. Supabase writes numbers as digits with the country code and no `+`.
* `sms.otp`, the code, sent as it is: 4 to 10 digits.
* `user.id`, kept as the verification's reference, so the Verify log finds the person the code was for.
* `metadata.ip_address`, the address of the person signing in, given to the per-IP cap of the [protection guard](/reliability#the-protection-guard).
* `metadata.uuid`, the request's identity. Supabase retries with a new `webhook-id` and the same `metadata.uuid`, so a retried request is sent and charged once.

## What Tawked answers

A code that went out is `200` with an empty object:

```json theme={"dark"}
{}
```

A refusal is also a `200`, because Supabase reads the reason only from a `200`. The body carries the status Supabase gives your app and the message it shows:

```json theme={"dark"}
{
  "error": {
    "http_code": 403,
    "message": "Tawked: «Your app» is in review. Until it is approved, codes reach the account owner's phone only."
  }
}
```

`«Your app»` stands for your application's English name.

| `http_code` | `message` | When |
| - | - | - |
| 401 | `Tawked could not verify the signature of this request. Copy the secret from the Supabase page in Tawked into the hook settings again.` | The signature does not match, or the timestamp is more than five minutes away. The only refusal that is a real `401` rather than a `200`. |
| 400 | `Tawked: the request carries no phone number or no code.` | No phone, or a code that is not 4 to 10 digits. |
| 403 | `Tawked: «Your app» cannot send codes. Its review was not approved, or the account is not active.` | The application was rejected, suspended or archived, or the account is not active. |
| 403 | `Tawked: the Supabase key was revoked in the Tawked console. Disconnect Supabase and connect it again from the application's Integrations.` | A connection key was revoked on the **API keys** tab. |
| 422 | `Tawked sends codes to Saudi mobile numbers only (+966 5…).` | The number is not a Saudi mobile. |
| 403 | `Tawked: «Your app» is in review. Until it is approved, codes reach the account owner's phone only.` | In review, to any number but the owner's. |
| 429 | `Tawked: the test codes allowed during review are used up. Codes reach everyone once «Your app» is approved.` | In review, the sandbox sends are used up. |
| 429 | `Tawked: too many codes to this number in the last hour. Try again later.` | The [per-destination cap](/reliability#rate-limits). |
| 402 | `Tawked: the account balance is too low. Top up the wallet in the Tawked console.` | The prepaid balance cannot pay for the code. |
| 403 | `Tawked paused «Your app» after a spike in new numbers. Resume it from the Tawked console.` | The new-destination guard paused the application. |
| 429 | `Tawked: «Your app» reached its daily spend cap.` | The daily spend cap. |
| 429 | `Tawked: too many codes from this address. Try again later.` | The per-IP cap, on `metadata.ip_address`. |
| 502 | `Tawked could not send the SMS. Try again.` | The SMS could not be sent. The code's charge is refunded. |
| 404 | `Tawked: this Supabase connection does not exist or was disconnected. Copy the hook address from the Supabase page in Tawked again.` | The Hook URL names no connection, or the connection was disconnected. |

## Limits

* **Saudi mobile numbers only** (`+9665…`). A Supabase project with numbers from other countries has to route those elsewhere itself: the hook has one address, and Tawked refuses every other number with `422`.
* **SMS only.** The hook carries no channel, so a code always goes by SMS, even when your app asked Supabase for WhatsApp.
* **Five seconds.** Supabase waits five seconds for the hook's answer. Tawked hands the SMS over for delivery within the call and gives the SMS gateway four seconds of it, so `{}` means the code left Tawked. A send that cannot be handed over in time is refused with `502` and refunded.
* **Rate limits.** The Verify caps apply to the hook's codes: 5 codes per number per hour by default and, once the application is approved, the per-IP cap, the daily spend cap and the new-destination guard (see [Reliability and limits](/reliability)). One connection takes up to 600 requests a minute; past that it answers `429` with `Retry-After`. Supabase's own SMS rate limits (**Authentication › Rate Limits**) apply before any request reaches Tawked.

## Troubleshooting

<AccordionGroup>
  <Accordion title="Supabase answers 500 &#x22;Hook requires authorization token&#x22;">
    Tawked could not match the signature, and Supabase shows a `401` from the hook this way. Copy the Secret from the Supabase page in Tawked into the hook settings again, the whole value from `v1,whsec_`. If you made a **New secret** in Tawked, it has to go into Supabase too. Check also that the server sending the request keeps the right time: a timestamp more than five minutes away is refused.
  </Accordion>

  <Accordion title="&#x22;is in review&#x22;: codes reach only one phone">
    Until the application is approved, codes reach the account owner's verified phone only, stamped as a test. Sign in with that phone to try the flow; codes reach every Saudi number once the review approves the application, with nothing to change in Supabase.
  </Accordion>

  <Accordion title="&#x22;the account balance is too low&#x22;">
    Each code is paid from the account's prepaid balance. Top up the wallet in the console; turn on the balance alert or auto top-up on the wallet's **Balance** page so sign-ins never stop.
  </Accordion>

  <Accordion title="&#x22;Saudi mobile numbers only&#x22;">
    The number is not a Saudi mobile, or your app passed it with a leading zero after the country code (`+9660 5…`). Pass numbers to `signInWithOtp` as `+9665` followed by 8 digits.
  </Accordion>

  <Accordion title="Nothing arrives and the Supabase page still waits">
    The request never reached Tawked. Check that the Send SMS hook is turned on, that its type is **HTTPS**, that the URL is the whole Hook URL, and that **Phone** is on in **Sign In / Providers**.
  </Accordion>
</AccordionGroup>

## Disconnecting and your data

* **Disconnect Supabase** revokes the connection's keys at once, so sign-in codes in your project stop until you connect again or turn the Send SMS hook off in Supabase. Codes already sent stay in the Verify log.
* Each code is a row of the Verify log: the number it went to, the Supabase user's id as its reference, and its outcome. Tawked reads nothing else from your Supabase project: only what each request carries.
* The Secret is stored encrypted and shown only to whoever manages the application's keys.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.