For developers

API & webhooks

Connect your website, store or CRM to Encore Text. The API and webhooks are included on every plan. The same rules apply as everywhere else in Encore Text: people are only added with consent, only subscribed contacts get texts, and STOP always works.

Getting started

  1. An owner or admin opens API & integrations in the app and creates a key with only the access it needs.
  2. Copy the key when it's shown — it's stored scrambled and can't be shown again. Revoke it anytime.
  3. Send it on every request as Authorization: Bearer etk_… (or an X-Api-Key header). Keep keys on your server, never in a web page.

Base URL

https://encoretext.com/api/public/v1
curl https://encoretext.com/api/public/v1/me \
  -H "Authorization: Bearer etk_your_key"

Access (scopes)

  • contacts:read — Look up contacts
  • contacts:write — Add and update contacts, tags and opt-outs
  • messages:send — Text subscribed contacts
  • campaigns:read — List campaigns and results

Requests and responses are JSON. Phone numbers can be any common US or Canadian format; responses use +1 format. Each key can make 120 requests a minute.

Endpoints

GET/me

Check that a key works.

Access: any key

Response

{ "workspace": { "id": "…", "name": "Northside Bakery" },
  "key": { "name": "Website", "prefix": "etk_1a2b3c4d", "scopes": ["contacts:write"] } }

GET/contacts?phone=+15551234567

Look up one contact by phone number.

Access: contacts:read

Response

{ "contact": { "id": "…", "phone": "+15551234567", "name": "Sam Lee", "email": null,
  "tags": ["vip"], "custom_fields": {}, "status": "subscribed", "source": "api",
  "subscribed_at": "2026-10-25T14:02:11Z", "opted_out_at": null, "created_at": "…" } }

POST/contacts

Add a subscriber, or update an existing contact's name, email, tags and custom fields.

Access: contacts:write

Request body

{ "phone": "+15551234567", "name": "Sam Lee", "email": "sam@example.com",
  "tags": ["website"], "custom_fields": { "favorite_item": "Rye loaf" },
  "consent": true }

Response

201 { "result": "subscribed", "contact": { … } }
200 { "result": "updated", "contact": { … } }
  • "consent": true is required to add someone new. Send it only when the person agreed to receive texts from you.
  • Workspaces that text about alcohol also need "age_confirmed": true (21 or older) for new subscribers.
  • Someone who opted out can't be re-added through the API (409 opted_out) — they can rejoin by texting START.
  • Updating a contact never changes their consent.

POST/contacts/tags

Add or remove tags. Adding a tag can start a "tag added" automation.

Access: contacts:write

Request body

{ "phone": "+15551234567", "add": ["vip"], "remove": ["lead"] }

Response

{ "contact_id": "…", "tags": ["website", "vip"] }

POST/contacts/opt-out

Opt a number out. It gets no more texts and any automations stop.

Access: contacts:write

Request body

{ "phone": "+15551234567" }

Response

{ "opted_out": true, "changed": true, "contact_id": "…" }

POST/messages

Text one subscribed contact.

Access: messages:send

Request body

{ "phone": "+15551234567", "body": "Your order is ready for pickup today until 6 PM.", "track_links": true }

Response

202 { "id": "…", "status": "sent" }
  • Only subscribed contacts can be texted (403 not_subscribed).
  • Your brand name is added at the start and "Reply STOP to opt out." at the end, like every other text.
  • Quiet hours (9 PM–8 AM in the person's time zone) return 409 quiet_hours with the time you can try again.
  • Uses credits at the normal rate and counts toward the 25,000-credit daily limit for automated and API texts (429 daily_limit).
  • Content our Acceptable Use Policy doesn't allow is refused (422 content_not_allowed). Your number must be approved to send.

GET/campaigns

Your 50 most recent campaigns with results.

Access: campaigns:read

Response

{ "campaigns": [ { "id": "…", "name": "Weekend sale", "status": "sent", "sent_at": "…",
  "recipients": 1200, "sent": 1200, "delivered": 1184, "failed": 16,
  "clicks": 212, "replies": 9, "opt_outs": 4 } ] }

Errors

Errors return { "error": "code", "message": "What happened" }.

400
invalid_body / invalid_json / invalid_phone — fix the request and try again.
401
missing_key / invalid_key — the key is missing, wrong or revoked.
402
insufficient_credits — add credits in Billing.
403
missing_scope / not_subscribed — the key can't do this, or the contact isn't subscribed.
404
not_found — no contact with that number.
409
opted_out / quiet_hours / on_hold / cannot_send — see the message for details.
413
body_too_large — request bodies can be up to 64 KB.
422
consent_required / age_confirmation_required / content_not_allowed / not_mobile.
429
rate_limited (over 120 requests a minute; see Retry-After) or daily_limit.
503
provider_unavailable — temporary; no credits were used. Retry later.

Webhooks

Add an HTTPS URL under API & integrations → Webhooks and choose events. We POST JSON to it and expect any 2xx response within 5 seconds. Failed deliveries are retried after 1 minute, 5 minutes, 30 minutes, 2 hours and 6 hours. After 50 failures in a row the webhook is turned off and you can turn it back on.

  • contact.subscribed — New subscriber: Someone joins your list (any signup page, form, keyword, import, API or Shopify).
  • contact.opted_out — Opt-out: A subscriber texts STOP or is opted out by your team, the API or Shopify.
  • message.received — Incoming reply: A contact texts your number.
  • message.status — Delivery status: A text you sent is delivered or fails.
POST https://your-site.example/encore-events
X-Encore-Signature: t=1793023331,v1=5f2b…
X-Encore-Event: contact.subscribed
X-Encore-Delivery: 8c1d…

{ "id": "8c1d…", "event": "contact.subscribed", "created_at": "2026-10-25T14:02:11Z",
  "workspace_id": "…",
  "data": { "contact_id": "…", "phone": "+15551234567", "name": "Sam Lee", "email": null,
            "tags": [], "source": "form", "subscribed_at": "2026-10-25T14:02:11Z" } }

Check the signature

Each webhook has its own signing secret (shown in the app). The X-Encore-Signature header holds a timestamp and an HMAC-SHA256 of <timestamp>.<raw body>. Reject requests that don't match or are more than 5 minutes old. Use the delivery id to ignore repeats.

// Node.js — verify an Encore Text webhook
import { createHmac, timingSafeEqual } from "node:crypto";

function verify(rawBody, header, secret) {
  const [, t, v1] = /^t=(\d+),v1=([a-f0-9]{64})$/.exec(header ?? "") ?? [];
  if (!t || Math.abs(Date.now() / 1000 - Number(t)) > 300) return false; // 5-minute window
  const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
  return timingSafeEqual(Buffer.from(expected), Buffer.from(v1));
}

Zapier

  • Trigger a Zap from Encore Text: use the "Webhooks by Zapier → Catch Hook" trigger, paste its URL as a webhook in Encore Text, then click "Send test event".
  • Add contacts or send texts from a Zap: use "Webhooks by Zapier → Custom Request" (POST) to an endpoint above with the header Authorization: Bearer etk_… and a JSON body.

Shopify

Connect a store under API & integrations → Shopify. You'll get a private URL to add as a webhook in Shopify (Settings → Notifications → Webhooks) for customer creation, customer update and order creation, plus a place to paste Shopify's signing key. We only add customers whose Shopify SMS marketing consent is "subscribed", and a Shopify SMS unsubscribe opts them out. No Shopify app install is needed.

Start an automation from your system

Automations with the "Webhook / API event" trigger have their own private URL and key (shown in the automation's trigger settings). Send the key as Authorization: Bearer … and POST { "phone": "+15551234567" } to it to start that automation for a subscribed contact.

Questions? Email support@encoretext.com. Use of the API is covered by our Terms of Service and Acceptable Use Policy.