# Migrate from Plivo to SimpleSMS

Source: https://joinsimplesms.com/migrate/plivo
Index: https://joinsimplesms.com/llms.txt

Moving SMS to SimpleSMS means three renames in the send call, a new webhook handler, and porting your numbers. The console importer reads your numbers, applications and Powerpacks and prepares the SimpleSMS side for review. This guide walks through the importer and then the code.

## What you need

- A SimpleSMS account with a sandbox key ([signup](/signup)).
- Your Auth ID (20 characters) and Auth Token.
- For porting: the authorized person's full name, the account number, and the last 4 digits of a port-out PIN if there is one. The importer pre-fills the account number field from your Auth ID.

## Run the importer

1. In the console open **Numbers**, then **Import from another provider**, and choose what you send: transactional, marketing, or both.
2. Choose **Plivo**, enter the Auth ID and Auth Token, and select **Scan account**. The scan reads your applications, numbers and Powerpacks with read-only requests.
3. Review the plan. Deselect anything you do not want.
4. Complete the port authorization and confirm.

### What the importer brings over

| In your account | Becomes in SimpleSMS |
| --- | --- |
| A number with SMS enabled | A port request, tagged `from-plivo` |
| The message URL of the application a number is attached to | A webhook endpoint subscribed to `message.received`, `message.opted_out` and `message.opted_in` |
| A Powerpack | A [sender pool](/docs/numbers) with the same name; its numbers join as their ports complete |
| A number's alias | The number's label |

The importer does not create an endpoint for delivery reports from this provider. Add one for `message.sent`, `message.delivered` and `message.failed` yourself in the console under **Webhooks**. If Powerpacks cannot be read, the numbers are still listed, without pools, and the plan says so.

## Change the code

Send with one JSON request and a bearer key:

```bash
curl -X POST https://api.joinsimplesms.com/v1/messages \
  -H "Authorization: Bearer $SIMPLESMS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"from": "+15005550100", "to": "+15005550006", "body": "Your code is on the way."}'
```

```javascript
import { SimpleSMS } from 'joinsimplesms';

const sms = new SimpleSMS(process.env.SIMPLESMS_API_KEY);

await sms.messages.send({
  from: '+15005550100',
  to: '+15005550006',
  body: 'Your code is on the way.',
});
```

| In your current code | In SimpleSMS |
| --- | --- |
| Auth ID and Auth Token as HTTP Basic auth | One API key: `Authorization: Bearer ssms_sk_...` |
| `src` | `from` |
| `dst` | `to` (one recipient per call; use a [batch](/docs/broadcasts) for many) |
| `text` | `body` |
| `powerpack_uuid` in place of `src` | A pool id (`pool_...`) as `from` |
| `url` on each send for delivery reports | A webhook endpoint on the account |
| `X-Plivo-Signature-V2` header with a nonce | `simplesms-signature` header: `t=<timestamp>,v1=<HMAC-SHA256>` |

Write numbers in E.164 with the plus sign (`+14155550132`). The API also accepts 10 digit US and Canada numbers and normalizes them.

## Rewrite the webhook handler

Inbound messages and delivery reports both arrive as signed JSON events at the endpoints registered on your account:

```javascript
import { verifyWebhook } from 'joinsimplesms';

// Express: mount with express.raw({ type: 'application/json' }) so req.body is the raw bytes.
app.post('/webhooks/sms', async (req, res) => {
  let event;
  try {
    event = await verifyWebhook({
      payload: req.body,
      signature: req.headers['simplesms-signature'],
      secret: process.env.SIMPLESMS_WEBHOOK_SECRET,
    });
  } catch {
    return res.status(400).end();
  }

  if (event.type === 'message.received') {
    // event.data has message_id, from, to and body
  }
  if (event.type === 'message.delivered' || event.type === 'message.failed') {
    // event.data.message_id is the message you sent
  }
  res.status(200).end();
});
```

SimpleSMS posts one JSON event per delivery: `{ id, type, created_at, data }`. Each endpoint has its own signing secret (`whsec_...`), shown next to the endpoint in the console. Respond with a 2xx within 5 seconds; a failed delivery is retried with backoff and can be replayed from the console ([webhooks](/docs/webhooks)).

To send one message to a list, create a [batch](/docs/broadcasts) of up to 10,000 recipients. It validates numbers, removes duplicates and opted-out recipients, and reports results per recipient.

## After the import

- **Keep your current service running until each port completes.** Running the plan creates things only on the SimpleSMS side. A number moves when the carrier completes its port, and each port shows its status in the console (`requested`, `submitted`, `foc_set`, `complete`, or `rejected` with the reason). See [Number porting](/docs/porting).
- **Register before you text the public.** A US local number can reach people other than your verified recipients only once it is linked to an approved brand and campaign. Start the [registration](/docs/compliance) while the ports are in flight; carrier review usually takes 3 to 7 business days.
- **Bring your opt-outs.** The importer does not copy a suppression list. Export yours and load it with `POST /v1/consent/import` (up to 500 numbers per call) or in the console, so nobody who said STOP hears from you again. From the first inbound message SimpleSMS enforces STOP, START and HELP itself ([opt-out and consent](/docs/opt-out)).
- **Rehearse in the sandbox.** A test key runs every endpoint with simulated delivery and real, signed webhooks, so the new handler can be finished before a single number has moved ([sandbox](/docs/sandbox)).

## What the importer does not move

- Message history, contacts and templates. Contacts can be imported separately from a CSV ([contacts](/docs/contacts)).
- Numbers outside the United States and Canada. They are listed and skipped.
- Numbers that are not SMS-enabled. SimpleSMS numbers are for SMS only.
- More than 2,000 numbers in one scan.

Your credentials are used for read-only requests during the scan and are not stored, logged or echoed back.

## Related

- [Batches and broadcasts](/docs/broadcasts)
- [Webhooks](/docs/webhooks)
- [API reference](/docs/api)
- [All migration guides](/migrate)
