Notify Africa

Templates

Create, validate, preview, synchronize, inspect, update, and delete WhatsApp templates.

WhatsApp templates are required for first-contact messages and messages outside the 24-hour customer-service window.

Lifecycle

Create and submit.
Pending.
Approved or rejected.
Send approved template.

Creating a WhatsApp template submits it to Meta immediately. There is no separate submit endpoint. Synchronization only refreshes the stored provider projection.

Authentication

All template operations require:

Authorization: Bearer ntfy_your_api_key
Content-Type: application/json

Omit Content-Type for requests without a body.

Create Template

POST /v1/waba-api/templates

Prerequisite: the Portal reports the WABA connection as ready.

curl --request POST \
  "$NOTIFY_API_BASE_URL/v1/waba-api/templates" \
  --header "Authorization: Bearer $NOTIFY_WABA_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{
    "channel": "whatsapp",
    "name": "Order Update",
    "description": "Order status notification",
    "externalTemplateName": "order_update",
    "externalTemplateLanguage": "en",
    "externalTemplateCategory": "UTILITY",
    "parameterFormat": "positional",
    "componentSchema": [
      {
        "type": "body",
        "text": "Hello {{1}}, your order {{2}} is ready.",
        "variables": ["1", "2"]
      }
    ],
    "variableSchema": [
      { "key": "1", "required": true, "source": "static", "component": "body" },
      { "key": "2", "required": true, "source": "static", "component": "body" }
    ]
  }'

Body

FieldRequiredValidation
channelyesSend whatsapp
nameyesString, 2-150 characters
descriptionnoString
externalTemplateNameyesLowercase letters, digits, underscores; maximum 512 characters
externalTemplateLanguageyesMeta locale in xx or xx_YY form
externalTemplateCategoryyesMARKETING, UTILITY, or AUTHENTICATION
parameterFormatnonamed or positional
componentSchemayesNon-empty component array
variableSchemanoArray of variable declarations

Success

HTTP 201 with the standard success envelope:

{
  "status": 201,
  "message": "Success",
  "timestamp": "2026-09-09T10:10:00.000Z",
  "path": "/v1/waba-api/templates",
  "data": {
    "id": "b3f1c2a0-1234-4a5b-9c6d-7e8f9a0b1c2d",
    "channel": "whatsapp",
    "sourceOfTruth": "waba",
    "status": "pending",
    "isActive": true,
    "name": "Order Update",
    "description": "Order status notification",
    "variableSchema": [
      { "key": "1", "required": true, "source": "static", "component": "body" },
      { "key": "2", "required": true, "source": "static", "component": "body" }
    ],
    "usageCount": 0,
    "whatsappConfig": {
      "wabaId": "9876543210",
      "externalTemplateId": null,
      "externalTemplateName": "order_update",
      "externalTemplateLanguage": "en",
      "externalTemplateCategory": "UTILITY",
      "parameterFormat": "positional",
      "componentSchema": [
        {
          "type": "body",
          "text": "Hello {{1}}, your order {{2}} is ready.",
          "variables": ["1", "2"]
        }
      ],
      "providerStatus": null,
      "statusInfo": null
    },
    "sync": {
      "syncStatus": null,
      "lastSyncedAt": null,
      "lastSyncError": null
    },
    "createdAt": "2026-09-09T10:10:00.000Z",
    "updatedAt": "2026-09-09T10:10:00.000Z"
  }
}

Common failures: invalid schema (400), duplicate external name/language (409), WABA not ready (422), provider rejection or unavailable dependency.

Next: use the returned data.id as templateId, then read status.

Components

ComponentFields
headerformat, text, sampleMediaServiceId, variables
bodytext, variables, addSecurityRecommendation
footertext, codeExpirationMinutes
buttonsbuttons

Text and placeholders

  • Body maximum: 1024 characters.
  • Header/footer maximum: 60 characters.
  • Header text must be one line and contain at most one placeholder.
  • Footer text cannot contain placeholders.
  • Positional placeholders must be contiguous from {{1}}.
  • Named placeholders use identifier-style names such as {{customer_name}}.
  • A placeholder cannot start or end component text.
  • Consecutive placeholders require text between them.
  • Do not place *, _, or ~ directly beside a placeholder.

Media headers

Header format accepts text, image, video, or document. Media headers require sampleMediaServiceId containing a durable Notify Africa Media Service UUID.

Buttons

TypeRules
quick_replyNon-empty unique label; maximum 25 characters; maximum 3 buttons
urlNon-empty unique label; HTTPS value; at most one {{1}} placeholder
phone_numberNon-empty unique label; optional + then 6-15 digits
otpAuthentication templates only; copy_code or one_tap
  • Do not mix quick replies with URL or phone buttons.
  • Use at most two combined URL/phone buttons.
  • When using two CTA buttons, use one URL and one phone button.
  • One-tap OTP requires packageName and signatureHash.
  • Authentication templates contain no header, use Meta-managed body/footer text, and contain exactly one OTP button.

List Templates

GET /v1/waba-api/templates

Always filter WABA documentation queries with channel=whatsapp.

curl --request GET \
  "$NOTIFY_API_BASE_URL/v1/waba-api/templates?channel=whatsapp&status=approved&currentPage=1&pageSize=10&sortOrder=desc" \
  --header "Authorization: Bearer $NOTIFY_WABA_API_KEY"

Query Parameters

ParameterRequiredRule
channelrecommendedUse whatsapp
statusnopending, approved, or rejected for WhatsApp
sourceOfTruthnoUse waba for provider-backed templates
qnoSearch string
currentPagenoPositive number; default 1
pageSizenoPositive number; default 10; capped at 25
sortBynoSort field
sortOrdernoasc or desc

Success

HTTP 200:

{
  "status": 200,
  "message": "Success",
  "timestamp": "2026-09-09T10:12:00.000Z",
  "path": "/v1/waba-api/templates?channel=whatsapp&currentPage=1&pageSize=10",
  "data": [
    {
      "id": "b3f1c2a0-1234-4a5b-9c6d-7e8f9a0b1c2d",
      "channel": "whatsapp",
      "sourceOfTruth": "waba",
      "status": "approved",
      "isActive": true,
      "name": "Order Update"
    }
  ],
  "pagination": {
    "total": 1,
    "currentPage": 1,
    "totalPages": 1,
    "pageSize": 10
  }
}

Common failures: invalid filter or sort value (400), invalid API key (401).

Next: retrieve a selected template or send one whose status is approved.

Get Template

GET /v1/waba-api/templates/:templateId

Path parameter: templateId, the Notify Africa UUID returned by create/list.

curl --request GET \
  "$NOTIFY_API_BASE_URL/v1/waba-api/templates/b3f1c2a0-1234-4a5b-9c6d-7e8f9a0b1c2d" \
  --header "Authorization: Bearer $NOTIFY_WABA_API_KEY"

Success: HTTP 200; data contains the complete template response shown under Create Template.

Common failures: malformed UUID (400), template absent or owned by another tenant (404).

Next: validate bindings, preview, synchronize, update, delete, or send the approved external template name.

Validate Bindings

POST /v1/waba-api/templates/:templateId/validate

This checks the supplied keys against the stored variableSchema. It does not submit the template or rerun all Meta component rules.

curl --request POST \
  "$NOTIFY_API_BASE_URL/v1/waba-api/templates/b3f1c2a0-1234-4a5b-9c6d-7e8f9a0b1c2d/validate" \
  --header "Authorization: Bearer $NOTIFY_WABA_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{
    "bindings": {
      "1": "Jane",
      "2": "ORD-1001"
    }
  }'

Body: optional bindings object keyed exactly like variableSchema[].key.

HTTP 201:

{
  "status": 201,
  "message": "Success",
  "timestamp": "2026-09-09T10:15:00.000Z",
  "path": "/v1/waba-api/templates/b3f1c2a0-1234-4a5b-9c6d-7e8f9a0b1c2d/validate",
  "data": {
    "valid": true,
    "errors": []
  }
}

Missing required static bindings and unknown keys return valid: false inside a successful response. Contact-derived variables are not required in this check.

Next: preview the same bindings.

Preview Template

POST /v1/waba-api/templates/:templateId/preview

The preview replaces matching placeholders locally. It is not a provider-rendered preview.

curl --request POST \
  "$NOTIFY_API_BASE_URL/v1/waba-api/templates/b3f1c2a0-1234-4a5b-9c6d-7e8f9a0b1c2d/preview" \
  --header "Authorization: Bearer $NOTIFY_WABA_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{"bindings":{"1":"Jane","2":"ORD-1001"}}'

HTTP 201:

{
  "status": 201,
  "message": "Success",
  "timestamp": "2026-09-09T10:16:00.000Z",
  "path": "/v1/waba-api/templates/b3f1c2a0-1234-4a5b-9c6d-7e8f9a0b1c2d/preview",
  "data": {
    "channel": "whatsapp",
    "renderedComponents": [
      {
        "type": "body",
        "text": "Hello Jane, your order ORD-1001 is ready.",
        "variables": ["1", "2"]
      }
    ]
  }
}

Common failures: malformed UUID (400), template absent or not owned (404).

Next: check provider status before sending.

Synchronize Template

POST /v1/waba-api/templates/:templateId/sync

Requests a provider projection refresh. It does not submit a draft. The immediate response can still contain the pre-refresh sync fields; retrieve status afterward.

curl --request POST \
  "$NOTIFY_API_BASE_URL/v1/waba-api/templates/b3f1c2a0-1234-4a5b-9c6d-7e8f9a0b1c2d/sync" \
  --header "Authorization: Bearer $NOTIFY_WABA_API_KEY"

Success: HTTP 201; data contains the complete template response.

Common failures: malformed UUID (400), non-WhatsApp template (400), template absent/not owned (404), provider or connection failure.

Next: call the status endpoint or process template.status.updated.

Get Template Status

GET /v1/waba-api/templates/:templateId/status

curl --request GET \
  "$NOTIFY_API_BASE_URL/v1/waba-api/templates/b3f1c2a0-1234-4a5b-9c6d-7e8f9a0b1c2d/status" \
  --header "Authorization: Bearer $NOTIFY_WABA_API_KEY"

HTTP 200:

{
  "status": 200,
  "message": "Success",
  "timestamp": "2026-09-09T10:18:00.000Z",
  "path": "/v1/waba-api/templates/b3f1c2a0-1234-4a5b-9c6d-7e8f9a0b1c2d/status",
  "data": {
    "id": "b3f1c2a0-1234-4a5b-9c6d-7e8f9a0b1c2d",
    "status": "approved",
    "isActive": true,
    "syncStatus": "synced",
    "lastSyncedAt": "2026-09-09T10:17:30.000Z",
    "lastSyncError": null
  }
}

Unknown provider states are exposed as pending in data.status. Retrieve the full template to inspect raw whatsappConfig.providerStatus and statusInfo.

Next: send when status is approved and isActive is true.

Update Template

PATCH /v1/waba-api/templates/:templateId

Mutable catalog fields: name, description, and isActive. Provider fields: externalTemplateCategory, parameterFormat, and componentSchema.

curl --request PATCH \
  "$NOTIFY_API_BASE_URL/v1/waba-api/templates/b3f1c2a0-1234-4a5b-9c6d-7e8f9a0b1c2d" \
  --header "Authorization: Bearer $NOTIFY_WABA_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{
    "name": "Order Update v2",
    "description": "Updated order notification"
  }'

HTTP 200; data contains the updated template.

Changing provider fields validates and resubmits the template, then resets its public state to pending. The external template name and language are not update fields.

Common failures: invalid component schema (400), template absent/not owned (404), provider rejection or unavailable dependency.

Next: wait for approval again after a provider-field update.

Delete Template

DELETE /v1/waba-api/templates/:templateId

curl --request DELETE \
  "$NOTIFY_API_BASE_URL/v1/waba-api/templates/b3f1c2a0-1234-4a5b-9c6d-7e8f9a0b1c2d" \
  --header "Authorization: Bearer $NOTIFY_WABA_API_KEY"

Success: HTTP 200 after provider deletion and local deactivation/soft deletion.

Common failures: malformed UUID (400), template absent/not owned (404), provider deletion failure.

Next: remove the external template name from sending configuration.

Send an Approved Template

Use whatsappConfig.externalTemplateName as template_name in POST /v1/waba-api/messages/template. The send worker performs provider/template preflight asynchronously, so preserve the message result and process later status events.

On this page