Notification Engine API documentation

Developer documentation

Notification API Guide

Tenant API connection and webhook reference

Server-to-server API

Send your first notification in minutes

Authenticate with an API key, choose a template, send through email or SMS, and track the final result.

GET Connect /connection GET Templates /templates POST Send /notifications/send GET Status /notifications/{id} GET / PUT Preferences /preferences/{external_id} EVENT Callbacks notification.finalized

Use the Notification API from your backend to send email and SMS notifications, check delivery status, list templates, manage recipient preferences, and receive delivery callbacks.

This is a server-to-server API. Do not expose API keys in browsers, mobile apps, client-side JavaScript, URLs, or public repositories.

Quick start

You need:

  • An API base URL, such as https://notifications.example.com/api/v1
  • An API key supplied by your Notification Engine administrator
  • An active template short code

Set these values in your backend secret manager:

NOTIFICATION_API_URL=https://notifications.example.com/api/v1
NOTIFICATION_API_KEY=ne_live_your_key

Verify the connection:

curl "${NOTIFICATION_API_URL}/connection" \
  --header "Authorization: Bearer ${NOTIFICATION_API_KEY}" \
  --header "Accept: application/json"

Send a notification:

curl --request POST "${NOTIFICATION_API_URL}/notifications/send" \
  --header "Authorization: Bearer ${NOTIFICATION_API_KEY}" \
  --header "Idempotency-Key: order-782-payment-received-v1" \
  --header "Content-Type: application/json" \
  --header "Accept: application/json" \
  --data '{
    "short_code": "payment_received",
    "recipient": {
      "external_id": "customer_123",
      "name": "Ali Khan",
      "email": "[email protected]",
      "phone": "+923001234567"
    },
    "variables": {
      "customer_name": "Ali Khan",
      "amount": "2500",
      "order_number": "782"
    },
    "channels": ["email", "sms"]
  }'

The API returns 202 Accepted with a notification ID. Use that ID to check status.

API overview

Method Endpoint Purpose
GET /connection Verify credentials and view usage
GET /templates List active templates and required variables
POST /notifications/send Send a notification
GET /notifications/{notification_id} Check notification and channel status
GET /preferences/{external_id} Get recipient preferences
PUT /preferences/{external_id} Update recipient preferences

All endpoints require API-key authentication. There is no portal-login API.

Authentication and headers

Use your API key as a Bearer token:

Authorization: Bearer ne_live_your_key
Accept: application/json

X-API-Key is also supported:

X-API-Key: ne_live_your_key

Do not send different values in both headers. The API returns 401 api_key_header_conflict when they conflict.

For JSON requests, also send:

Content-Type: application/json

Request IDs

You may provide a UUID or ULID:

X-Request-Id: 0190c89a-2fc0-7f22-bdc8-02c82d1ab321

Every API response contains X-Request-Id. Include it when reporting an integration problem.

Verify connection

GET /connection

Use this endpoint during setup and health checks. capabilities.channels lists only channels currently enabled for the platform and connected/activated for your tenant. sms appears when your tenant uses platform SMS (SNS/dry-run ready) or has active custom Twilio credentials. slack and web appear only after your administrator connects them.

Example response:

{
  "connected": true,
  "tenant": {
    "id": "8dbddf53-442a-4f8f-a559-57211f338f02",
    "name": "Example Company",
    "status": "active",
    "timezone": "Asia/Karachi"
  },
  "environment": "production",
  "capabilities": {
    "channels": ["email", "sms", "slack", "web"],
    "webhook_callbacks": true,
    "idempotency": true
  },
  "usage": {
    "period": "2026-07",
    "used": 318,
    "limit": 1000,
    "remaining": 682
  }
}

List templates

GET /templates

The response lists active templates available to your account. Use short_code when sending and provide every key listed in variables.

Example response:

{
  "data": [
    {
      "short_code": "payment_received",
      "name": "Payment Received",
      "category": "transactional",
      "description": "Confirms a customer payment.",
      "allowed_channels": ["email", "sms"],
      "variables": [
        "customer_name",
        "amount",
        "order_number"
      ],
      "content": {
        "email": { "has_html": true, "has_text": true },
        "sms": { "configured": true, "max_length": 170 }
      }
    }
  ]
}

The current API returns all active templates. Filtering and pagination are not supported.

Every template now supports per-channel content (email HTML/text, SMS, web title/body, Slack) in addition to the legacy subject/body pair; the send API request/response shape is unchanged. The additive content object reports, for each allowed channel, whether content is configured and the rendered length limit that applies — it never includes the actual template content. Absent keys mean the channel is not in allowed_channels.

Send a notification

POST /notifications/send

Request

{
  "short_code": "payment_received",
  "recipient": {
    "external_id": "customer_123",
    "name": "Ali Khan",
    "email": "[email protected]",
    "phone": "+923001234567",
    "slack_user_id": "U012ABCDEF",
    "slack_channel_id": "C012ABCDEF",
    "web_client_id": "customer_123",
    "web_channel": "tenant:uuid:user:customer_123"
  },
  "variables": {
    "customer_name": "Ali Khan",
    "amount": "2500",
    "order_number": "782"
  },
  "channels": ["email", "sms", "slack", "web"],
  "callback_url": "https://customer.example.com/webhooks/notifications"
}

Fields

Field Required Description
short_code Yes Active template short code
recipient.external_id Yes Your stable recipient ID, maximum 100 characters
recipient.name No Recipient name, maximum 150 characters
recipient.email For email Valid email address
recipient.phone For SMS E.164 number, such as +923001234567
recipient.slack_user_id For Slack when no channel/default Slack member ID (U… / W…)
recipient.slack_channel_id For Slack when no user/default Slack channel ID (C… / G… / D…)
recipient.web_client_id For web per tenant mode Ably client ID
recipient.web_channel For web per tenant mode Ably channel name
variables Yes Values required by the selected template
channels No Subset of email, sms, slack, web (max 4); template defaults when omitted
callback_url No HTTPS endpoint for the final status callback

metadata is not supported.

The effective channels are limited by platform enablement, tenant channel configuration, the template, requested channels, and recipient preferences. The API returns 422 no_channels_available if no channel remains. Platform-disabled channels return 422 channel_disabled. Unconfigured SMS (incomplete custom Twilio) / Slack / Web returns 422 channel_not_configured. Missing Slack/web destinations return 422 slack_destination_missing / 422 web_destination_missing.

SMS credentials are configured in the admin SMS Settings screen (platform SNS or custom Twilio). There is no public API to submit Twilio credentials per send.

Quota remains one monthly unit per accepted notification, regardless of how many channels are selected.

Idempotent retries

Send a stable Idempotency-Key for each business event:

Idempotency-Key: order-782-payment-received-v1

The key is optional, printable ASCII, and limited to 128 characters.

  • Same key and same request: returns the original notification without using quota again
  • Same key and different request: returns 409 idempotency_key_conflict
  • No key: every request creates a new notification

Accepted response

{
  "message": "Notification accepted for processing.",
  "notification_id": "e909f77f-a7d5-4c90-b401-33d6984f751b",
  "status": "accepted",
  "selected_channels": ["email", "sms"]
}

An accepted request is processed asynchronously. It does not mean the provider has sent it yet.

Check notification status

GET /notifications/{notification_id}

Example:

curl "${NOTIFICATION_API_URL}/notifications/e909f77f-a7d5-4c90-b401-33d6984f751b" \
  --header "Authorization: Bearer ${NOTIFICATION_API_KEY}" \
  --header "Accept: application/json"

Example response:

{
  "notification_id": "e909f77f-a7d5-4c90-b401-33d6984f751b",
  "short_code": "payment_received",
  "status": "partial_failed",
  "callback_status": "pending",
  "recipient": {
    "external_id": "customer_123",
    "name": "Ali Khan",
    "email": "[email protected]",
    "phone": "+923001234567"
  },
  "requested_channels": ["email", "sms"],
  "selected_channels": ["email", "sms"],
  "deliveries": [
    {
      "channel": "email",
      "status": "sent",
      "provider_message_id": "safe-provider-id",
      "attempt_count": 1,
      "failure_reason": null,
      "sent_at": "2026-07-22T00:15:04Z"
    },
    {
      "channel": "sms",
      "status": "failed",
      "provider_message_id": null,
      "attempt_count": 1,
      "failure_reason": "The SMS destination was rejected.",
      "sent_at": null
    }
  ],
  "accepted_at": "2026-07-22T00:15:00Z",
  "finalized_at": "2026-07-22T00:15:05Z"
}

Status values

Status Meaning
accepted Request stored and awaiting queue processing
queued Delivery work queued
sending At least one channel is processing
sent All selected channels were accepted by their providers
partial_failed Some channels sent and some failed
failed All selected channels failed

Terminal statuses are sent, partial_failed, and failed.

sent means the configured provider accepted the message. It does not guarantee recipient delivery or reading.

Recipient preferences

Preferences are stored against your external_id and a template.

Get preferences

GET /preferences/{external_id}
{
  "external_id": "customer_123",
  "preferences": [
    {
      "template_short_code": "payment_received",
      "recipient_name": "Ali Khan",
      "recipient_email": "[email protected]",
      "recipient_phone": "+923001234567",
      "recipient_slack_user_id": "U012ABCDEF",
      "recipient_web_client_id": "customer_123",
      "email_enabled": true,
      "sms_enabled": false,
      "slack_enabled": true,
      "web_enabled": false,
      "updated_at": "2026-07-22T00:00:00Z"
    }
  ]
}

An empty preferences array means no overrides exist; template channels are enabled by default.

Update preferences

PUT /preferences/{external_id}
{
  "preferences": [
    {
      "template_short_code": "payment_received",
      "recipient_name": "Ali Khan",
      "recipient_email": "[email protected]",
      "recipient_phone": "+923001234567",
      "email_enabled": true,
      "sms_enabled": false,
      "slack_enabled": true,
      "web_enabled": false,
      "recipient_slack_user_id": "U012ABCDEF",
      "recipient_web_client_id": "customer_123"
    }
  ]
}

The API atomically creates or updates the submitted template rows. Existing rows omitted from the request are retained.

Webhook callbacks

When callback_url is included in a send request, the engine sends one event type after the notification reaches a terminal status:

notification.finalized

Callbacks are delivered at least once. Deduplicate them using event and notification_id.

Callback headers

Content-Type: application/json
X-Notification-Event: notification.finalized
X-Notification-Timestamp: 1784679300
X-Notification-Signature: sha256=lowercase_hex_hmac
X-Notification-Id: e909f77f-a7d5-4c90-b401-33d6984f751b
X-Request-Id: 0190c89a-2fc0-7f22-bdc8-02c82d1ab321

Verify the signature

The signed value is:

{X-Notification-Timestamp}.{raw_request_body}

Calculate:

HMAC-SHA256(webhook_signing_secret, signed_value)

Then prefix the lowercase hex result with sha256=.

Your webhook should:

  1. Read the raw body before JSON parsing.
  2. Reject timestamps older or newer than five minutes.
  3. Compare signatures using a constant-time function.
  4. Return a 2xx response quickly.
  5. Process the event asynchronously and idempotently.

X-Request-Id is for correlation and is not part of the signature.

Callback payload

{
  "event": "notification.finalized",
  "notification_id": "e909f77f-a7d5-4c90-b401-33d6984f751b",
  "short_code": "payment_received",
  "status": "partial_failed",
  "recipient": {
    "external_id": "customer_123",
    "email": "[email protected]",
    "phone": "+923001234567"
  },
  "deliveries": [
    {
      "channel": "email",
      "status": "sent",
      "provider_message_id": "safe-provider-id"
    },
    {
      "channel": "sms",
      "status": "failed",
      "provider_message_id": null
    }
  ],
  "occurred_at": "2026-07-22T00:15:05Z"
}

Errors and retries

Errors use a stable JSON shape:

{
  "code": "validation_failed",
  "message": "The request data is invalid.",
  "details": {
    "fields": {
      "recipient.email": [
        "The recipient.email field must be a valid email address."
      ]
    }
  }
}

Common errors:

HTTP Code Action
401 api_key_required Add an authentication header
401 api_key_invalid Check the key and environment
401 api_key_expired Request a replacement key
401 api_key_revoked Request a replacement key
403 tenant_inactive / tenant_suspended Contact your administrator
404 notification_not_found Check the notification ID and account
404 template_not_found Refresh the template list
409 idempotency_key_conflict Use the original payload or a new business key
422 validation_failed Correct the fields in details.fields
422 template_variables_invalid Match the template variable list
422 template_render_failed Contact your administrator; the template failed safe rendering
422 sms_body_too_long / web_title_too_long / web_body_too_long / slack_body_too_long The rendered channel content exceeds its configured limit; ask your administrator to shorten the template or variable values
422 template_channel_content_missing A selected channel is allowed on the template but has no content configured; contact your administrator
422 no_channels_available Check template channels and preferences
422 channel_disabled Channel is disabled platform-wide; contact your administrator
422 channel_not_configured Ask your administrator to configure SMS (platform or Twilio), or connect Slack/web for your account
422 slack_destination_missing Provide recipient.slack_user_id or recipient.slack_channel_id, or set a tenant default
422 web_destination_missing Provide recipient.web_client_id / recipient.web_channel matching the tenant publish mode
422 callback_url_invalid / callback_url_forbidden Use a safe public HTTPS endpoint
429 rate_limit_exceeded Wait and retry with backoff
429 monthly_limit_exceeded Contact your administrator about quota
500 internal_error Retry safely using the same idempotency key
503 service_unavailable Retry safely using the same idempotency key

Retry only temporary failures (429, 500, and 503) with exponential backoff and jitter. Reuse the same Idempotency-Key.

Do not retry validation, authentication, permission, or idempotency-conflict errors without correcting the request.

Go-live checklist

  • Store the API key in a backend secret manager.
  • Never expose the API key in frontend code.
  • Verify /connection in each environment.
  • Fetch templates and validate required variables.
  • Use a stable idempotency key for every business event.
  • Store returned notification IDs.
  • Poll status with bounded backoff or implement callbacks.
  • Verify webhook signatures against the raw request body.
  • Handle duplicate callbacks idempotently.
  • Log X-Request-Id for support and troubleshooting.