# Connect 23 Telecom to Customer.io

> Configure a simple Customer.io SMS webhook with a flat request, automatic retry safety, sandbox-first testing and delivery tracking in 23 Telecom.
> Source: https://docs.23telecom.co.uk/integrations/customer-io/
> Activation: Managed activation

Instructions for LLMs: This is one page of the 23 Telecom messaging API docs
(SMS today; more channels planned). Base URL: https://restlink23telecom.com/api/v1,
auth via the X-API-Key header. Match errors on the error_code field, never on
description text. Full docs: https://docs.23telecom.co.uk/llms-full.txt · Schemas: https://docs.23telecom.co.uk/openapi.yaml

| | |
| --- | --- |
| **Setup type** | Webhook · self-service |
| **Setup time** | About 10 minutes |
| **Sends SMS** | Yes |
| **Delivery status in Customer.io** | No — check 23 Telecom |
| **Who can configure** | Customer.io Workspace Manager |

  23 Telecom supports Customer.io through our strict, retry-safe integration endpoint,
  including Customer.io's automatic `X-CIO-Idempotency-Key`. You can start
  testing immediately in sandbox. When you're ready to launch, contact your 23
  Telecom account manager to enable live sending and complete a short
  configuration check.

## What this connection does

You add 23 Telecom as a **reusable webhook** in Customer.io, then call it from a
workflow's **Send and receive data** step to send an SMS. Customer.io shows you
the flat API response. An HTTP `202` means 23 Telecom **accepted** the message;
carrier delivery reports stay in 23 Telecom.

## Before you start

- A Customer.io **Workspace Manager** seat (creating webhooks is a workspace
  setting).
- An [active workspace](https://docs.23telecom.co.uk/account/workspaces), an
  [approved sender ID](https://docs.23telecom.co.uk/sms/sender-id) and a test number in E.164 format, e.g.
  `+447911123456`.
- A restricted 23 Telecom [API key](https://docs.23telecom.co.uk/api-keys) with only the `sms.send`
  permission — start with a **sandbox** `sk_test_` key
  ([Sandbox & test mode](https://docs.23telecom.co.uk/sandbox)).

`POST /api/v1/integrations/sms/send` (permission: `sms.send`)

## Connect 23 Telecom

1. In Customer.io open **Settings → Workspace Settings → Webhook configuration**
   and select **Create webhook**.

2. Set **Name** to `23 Telecom SMS`, **Method** to `POST`, and the endpoint URL
   to `https://restlink23telecom.com/api/v1/integrations/sms/send`.

3. Add only two headers — `Content-Type: application/json` and your secret
   `X-API-Key`. Do **not** add `Idempotency-Key` or
   `X-CIO-Idempotency-Key`: Customer.io adds its retry key automatically.
   Saving this as a **reusable webhook** lets campaign authors select it later
   **without seeing the API key**.

4. In your workflow, add a **Send and receive data** action and pick the saved
   `23 Telecom SMS` webhook.

5. Paste the request body below. There are only three fields to map.

6. Set the action to **Draft**, then run a test with a sandbox key.

### Request body

Use Customer.io's official `to_json` filter so quotes and line breaks never
break the JSON:

```liquid title="Send and receive data → body"
{% capture sms_text -%}
Hi {{ customer.first_name | default: "there" }}, your message goes here.
{%- endcapture %}
{
  "to": {{ customer.phone | to_json }},
  "message": {{ sms_text | strip | to_json }},
  "sender_id": "YourBrand"
}
```

### Field mapping

| Customer.io field | Value |
| --- | --- |
| Method | `POST` |
| Endpoint URL | `https://restlink23telecom.com/api/v1/integrations/sms/send` |
| `Content-Type` | `application/json` |
| `X-API-Key` | your restricted 23 Telecom key (sandbox first) |
| Idempotency | automatic `X-CIO-Idempotency-Key`; do not add a header yourself |
| `to` | recipient phone in E.164 (see `to_json` in the body above) |
| `message` | your Liquid message text |
| `sender_id` | your approved sender ID |

## Send a test

1. Keep the action in **Draft** and use your `sk_test_` key. Leave idempotency
   headers out of the webhook configuration; Customer.io supplies its automatic
   key on the request.

2. Run the workflow against **one** test profile whose `phone` is your E.164
   test number.

3. Expect HTTP `202` with a flat response containing `status: "accepted"`, a
   non-empty `message_id`, and `mode: "sandbox"`. Sandbox simulates the send —
   no live SMS or carrier delivery is created.

4. Save `response.message_id` as a journey attribute so you can correlate the
   message later.

  On this strict endpoint, HTTP `202` with `status: "accepted"` and a
  `message_id` guarantees durable API acceptance. It does not mean the handset
  received the SMS. A blocked destination returns `422` before acceptance.
  Missing local pricing is accepted fail-open; the later delivery report
  supplies the final carrier status and cost.

  Customer.io holds the webhook open for ~16 seconds and **automatically retries**
  timeouts and `408/409/429/5xx` up to 11 times over roughly an hour, tagging each
  attempt with a value that is unique per message and stable across its retries.
  23 Telecom accepts that automatic `X-CIO-Idempotency-Key` and retains the
  completed response for 24 hours. An exact retry returns the same `message_id`
  with `Idempotent-Replayed: true`; changing the body returns `409`. Never add a
  second idempotency source and do not manually re-run an old delivery after the
  24-hour replay window.

## Production activation

Ready to go live? Your 23 Telecom account manager enables the exact workspace
and confirms a dedicated workspace-bound key with only `sms.send`. The endpoint,
request body, existing API keys and HMAC settings remain unchanged.

We then complete a short launch check covering automatic retries, lost-response
replay and delivery tracking. Keep the action in **Draft** with your sandbox
`sk_test_` key while activation is completed; then replace the key and publish
the workflow.

## Verify the result

- **Accepted** — HTTP `202` with `status: "accepted"` and `message_id` — means
  23 Telecom durably queued the message, not that it was delivered.
- **Delivered** is a later carrier report. Check it in
  [Message status](https://docs.23telecom.co.uk/sms/status) with the `message_id`, or in
  [Statistics](https://docs.23telecom.co.uk/sms/statistics). For a live DLR feed, add a
  [delivery webhook](https://docs.23telecom.co.uk/webhooks/delivery) in 23 Telecom.

## Troubleshooting

| Response | Cause & fix |
| --- | --- |
| `400 IDEMPOTENCY_KEY_REQUIRED` | Customer.io did not supply its automatic retry key — keep the manual idempotency fields empty and contact support |
| `400 MULTIPLE_IDEMPOTENCY_KEYS` | Remove the manually configured `Idempotency-Key` or `X-CIO-Idempotency-Key` |
| `400 INVALID_IDEMPOTENCY_KEY` | The automatic key is outside the supported visible-ASCII contract — contact support |
| `401 INVALID_API_KEY` | Wrong or disabled key in the secret header |
| `403 INTEGRATION_NOT_ENABLED` | Live access is not enabled for this workspace — contact your account manager |
| `409 IDEMPOTENCY_KEY_REUSED` | Same key sent with a different body — use a fresh unique key |
| `429 RATE_LIMIT_EXCEEDED` | Honor `Retry-After`; Customer.io should retry the exact request automatically |

## Need help?

- **Webhook, workflow or Liquid** questions — Customer.io support.
- **API key, sender ID, `accepted` but not `delivered`** — your 23 Telecom
  account manager.

## Official platform documentation

- [Customer.io webhook actions](https://docs.customer.io/messaging/send/workflows/webhooks/action/)
- [Reusable webhooks (manager)](https://docs.customer.io/messaging/send/workflows/webhooks/manager/)
- [Liquid tags & filters](https://docs.customer.io/journeys/liquid/tag-list/?version=latest)

*Last verified: 16 July 2026.*