Quickstart

Send your first SMS broadcast in under five minutes.
View as Markdown

1. Get an API key

Sign in to the Teekrr dashboard, go to API Management → API Keys, and create a key with the scopes you need.

ScopeEndpoints
send_smsPOST /sms
send_whatsappPOST /whatsapp, POST /whatsapp/upload-header-image
send_emailPOST /email
(none)POST /broadcasts/validate - any valid key

The plaintext key value is shown only once at creation. Teekrr stores only a hash of it. Lost keys cannot be recovered - revoke and reissue.

2. Send your first SMS

Every SMS broadcast needs a registered keyword (your sender-ID prefix) - pass it as keyword, or as keywordUid if you have the UUID. Your keywords are listed in the dashboard.

$curl -X POST https://api.teekrr.com/sms \
> -H "Authorization: Bearer $TEEKRR_API_KEY" \
> -H "Content-Type: application/json" \
> -d '{
> "templateContent": "Hi from Teekrr!",
> "campaignName": "My first broadcast",
> "type": "quick broadcast",
> "keyword": "TEEKRR",
> "recipients": ["60123456789"]
> }'

Expected response (202 Accepted):

1{
2 "data": {
3 "broadcastUuid": "00000000-0000-0000-0000-000000000001",
4 "queued": 1,
5 "failed": 0,
6 "totalSmsUnits": 1,
7 "sqsMessageIds": ["11111111-2222-3333-4444-555555555555"]
8 }
9}

The broadcast is accepted asynchronously - final delivery status reaches your registered webhook URL.

Passing templateContent without templateName creates a one-off template named after your campaignName. To reuse an existing template instead, pass templateName or templateUid.

3. Register a webhook

In the dashboard, go to API Management → Webhooks → New Webhook and provide:

  • A URL on your server (publicly reachable, HTTPS)
  • The events you want to receive: delivered, failed, sent, read, inbound
  • Optionally, the channels to narrow to (sms, whatsapp, email) - leave it unset to receive every channel

You’ll receive a plaintext signing secret once. Store it server-side and use it to verify the X-Teekrr-Signature header on every incoming webhook - see the Webhooks guide for verification snippets in Node, Python, and Go.

sent, read, and inbound are WhatsApp-only, and the email channel emits no webhooks at all - the sent / failed / total counts in the POST /email response are the complete result.

Status events fire only for broadcasts sent through the API. A broadcast composed in the dashboard emits nothing, which is the usual reason a freshly registered webhook stays quiet.

4. Send a WhatsApp broadcast

WhatsApp is template-only - templateName must reference a template your client owns that has already been approved. Templates are created and submitted for approval in the dashboard.

Template values go in a single named map, variables.templateParams, keyed by the {{name}} placeholders the template declares:

$curl -X POST https://api.teekrr.com/whatsapp \
> -H "Authorization: Bearer $TEEKRR_API_KEY" \
> -H "Content-Type: application/json" \
> -d '{
> "templateName": "welcome_promo_v1",
> "campaignName": "My first WhatsApp broadcast",
> "type": "quick broadcast",
> "recipients": ["60123456789"],
> "variables": {
> "templateParams": { "name": "Ali", "discount": "RM50 OFF" }
> }
> }'

variables accepts templateParams and nothing else. Sending header, body, button, headerImage, or bodyParams is rejected with 400, not ignored.

Media headers and dynamic URL buttons are ordinary named variables: upload the asset via POST /whatsapp/upload-header-image and pass the returned url as the value of whichever variable the template named.

5. Send to many recipients (dynamic mode)

To personalize content per recipient, use recipientVariables:

$curl -X POST https://api.teekrr.com/sms \
> -H "Authorization: Bearer $TEEKRR_API_KEY" \
> -H "Content-Type: application/json" \
> -d '{
> "templateName": "otp_login",
> "campaignName": "Login OTPs 2026-05-06",
> "type": "quick broadcast",
> "keyword": "TEEKRR",
> "recipientVariables": [
> { "msisdn": "60123456789", "variables": { "code": "318204", "name": "Ali" } },
> { "msisdn": "60198765432", "variables": { "code": "771943", "name": "Fatimah" } }
> ]
> }'

The template content Hi {name}, your OTP is {code} is resolved per-recipient before sending. Maximum 10,000 recipients per request, and duplicates are rejected, not silently dropped.

6. Pre-flight a large list

Before committing a big recipient list, dry-run it. POST /broadcasts/validate runs the same validator the send endpoints use - no credit reserved, no messages created, nothing enqueued:

$curl -X POST https://api.teekrr.com/broadcasts/validate \
> -H "Authorization: Bearer $TEEKRR_API_KEY" \
> -H "Content-Type: application/json" \
> -d '{
> "channel": "sms",
> "recipients": ["60123456789", "0123456789", "not-a-number"]
> }'
1{
2 "data": {
3 "summary": { "total": 3, "valid": 1, "invalid": 2 },
4 "valid": [{ "row": 1, "value": "+60123456789" }],
5 "invalid": [
6 { "row": 2, "line": 2, "field": "msisdn", "value": "0123456789",
7 "code": "DUPLICATE", "message": "Duplicate recipient (also on row 1)" },
8 { "row": 3, "line": 3, "field": "msisdn", "value": "not-a-number",
9 "code": "INVALID_MSISDN", "message": "Must be a valid phone number (E.164 or Malaysia 01X format)" }
10 ]
11 }
12}

Malaysian numbers in local 01X… form are normalised to +60…, which is why row 2 collides with row 1.

What’s next