# Receiving SMS Messages

> Receiving your first SMS with Pinnacle's TypeScript SDK

## Prerequisites

Before proceeding, ensure you have obtained a phone number and API key as described in the [prerequisites](/quickstart/sms).

## Installation

Initialize a new Node.js project:

```bash
npm init -y
```

Install the Pinnacle TypeScript SDK and Express:

```bash
npm i express dotenv rcs-js@2.0.25
npm i --save-dev @types/express @types/node tsx
```

**Info:**
  This guide uses version `rcs-js 2.0.25`. It's compatible with the following
  runtimes: Node.js 18+, Vercel, Cloudflare Workers, Deno v1.25+, Bun 1.0+, and
  React Native.

## Configuration

Create an `.env` file in your project root and add your Pinnacle API key and signing secret:

```bash
PINNACLE_API_KEY="your_api_key" # pnclk_
SENDER_NUMBER="your_phone_number" # +12345678910
PINNACLE_SIGNING_SECRET="your_signing_secret" # pss_
```

## Setting Up a Webhook

To receive inbound SMS messages, you need to configure a webhook in the Pinnacle dashboard:

1. Navigate to **Development > Webhooks** in the Pinnacle dashboard
2. Click **Create new webhook**
3. Give your webhook a descriptive name
4. Enter your webhook endpoint URL
   - For local development, use an ngrok tunnel pointing to `localhost:3000/inbound-sms`
   - For production, use your deployed server URL
5. After creation, copy the **signing secret** and add it to your `.env` file
6. Attach a phone number to your webhook to receive messages. If the number is a sandbox number, ensure that you've whitelisted a number and verified the 4 digit PIN.

**Info:**
  Optionally, you can configure custom HTTP headers (e.g. `X-API-KEY`) to be
  sent on every webhook delivery. Add them in the dashboard or via the
  `headers` field on [`POST /webhooks/attach`](/api-reference/webhooks/attach-webhook).
  The `PINNACLE-SIGNING-SECRET` header is reserved.

## Creating Your Webhook Endpoint

Create a new TypeScript file (e.g., `index.ts`) and use the [complete example](#complete-example).

The complete example creates an Express endpoint that:

- Receives webhook POST requests at `/inbound-sms`
- Verifies the webhook signature using your signing secret
- Processes incoming message events
- Handles both received messages and message status updates

## Running Your Server

Start the server:

```bash
npx tsx index.ts
```

Your server will start on `http://localhost:3000`. If you're using ngrok for local development, start it in a separate terminal:

```bash
ngrok http 3000
```

Use the ngrok URL (e.g., `https://abc123.ngrok.io/inbound-sms`) as your webhook endpoint in the Pinnacle dashboard.

## Testing Your Webhook

Send an SMS to your Pinnacle phone number from any mobile device. You should see the message logged in your server console:

```text
Received message from +14155551234: Hello, this is a test!
```

**Warning:**
  If you're not receiving any messages, make sure you have a phone number
  associated with your webhook.

Your webhook should now be successfully receiving inbound SMS messages as well message status updates for outbound messages!

For more detail about processing the message payload received, please view the [process method](/methods/process).

Optionally, send a POST request to `/send-sms/:phoneNumber` to send an initial SMS message out. Use this unauthenticated demo endpoint only for local testing.

## Complete example

```typescript
import express from "express";
import { PinnacleClient, Pinnacle } from "rcs-js";
import dotenv from "dotenv";
dotenv.config();
const port = 3000;

const app = express();
const client = new PinnacleClient({ apiKey: process.env.PINNACLE_API_KEY! });

async function sendSMS(recipient: string) {
  return client.messages.sms.send({
    from: process.env.SENDER_NUMBER!,
    to: recipient,
    text: "Hello, world!",
  });
}

app.post("/send-sms/:phoneNumber", async (req, res) => {
  const { phoneNumber } = req.params;
  const result = await sendSMS(phoneNumber);
  res.json(result);
});

app.post("/inbound-sms", express.json(), async (req, res) => {
  try {
    // Process and validate the webhook
    // Returns a fully typed MessageEvent or UserEvent object
    // UserEvent is mainly used to know when the user is typing
    const messageEvent =
      await client.messages.process(req); // automatically detects PINNACLE_SIGNING_SECRET in your env vars

    // messageEvent is now typed as Pinnacle.MessageEvent
    // Your business logic here
    await handleInboundMessage(messageEvent);

    res.status(200).json({ status: "processed" });
  } catch (error) {
    console.error("Unable to process webhook", error);
    res.status(400).json({ error: "Unable to process webhook" });
  }
});

async function handleInboundMessage(
  event: Awaited<ReturnType<typeof client.messages.process>>
) {
  console.log(event);
  switch (event.type) {
    case "MESSAGE.RECEIVED":
      if (event.direction == "INBOUND") {
        // Type guard to check if message is SMS (discriminated by type field)
        if (event.message.type === "SMS") {
          const messageText = event.message.text;
          console.log("Received message:", messageText);

          if (messageText === "hello") {
            await sendSMS(event.conversation.from);
          }
        }
        break;
      }

    case "MESSAGE.STATUS":
      break;

    case "USER.TYPING":
      break;
  }
}

app.listen(port, () => {
  console.log(`Example app listening on port ${port}`);
});
```
