Skip to content

API Authentication

All requests to the Abelo REST API must be authenticated. Abelo supports two authentication mechanisms:

  • Bearer Token Authentication — the simplest option and recommended for most server-to-server integrations.
  • HMAC-SHA256 Request Signing — provides additional request integrity and protection against unauthorized request modification or replay when stronger authentication controls are required.

1. Generating API Keys

API keys are created and managed in the Abelo CMS.

  1. Log in to the Abelo dashboard and navigate to Settings > Developer > API Keys. This requires the Owner or Admin role.
  2. Click Create API Key.
  3. Enter a descriptive label for the key, such as Production Backend or Staging Integration.
  4. Select the authentication method (Bearer or HMAC) and assign the required API scopes.
  5. Create the key and securely store the generated Key ID, Secret and Webhook Signing Secret. You can use the latter in case you need to override target URL of a given webhook for messaging events. Check more in Triggering API Campaig

Store Secrets Securely

API secrets are displayed only once when the key is created. Abelo stores secrets securely using Argon2id hashing and does not allow them to be retrieved later through the CMS or API.

If a secret is lost or compromised, revoke or rotate the affected API key and generate a new one.

Never Expose API Secrets

API credentials are intended for server-side use only. Never include API secrets in client-side JavaScript, mobile applications, browser code, source repositories, or other publicly accessible locations.


2. Authentication Methods

Bearer Token Authentication is the simplest authentication method and is recommended for most server-to-server integrations.

Include the API credentials in the Authorization header using the following format:

Authorization: Bearer <key_id>:<key_secret>

Example Request

curl -X GET https://api.abelo.ai/v1/profiles \
  -H "Authorization: Bearer ak_01J8K92M4:as_9f8a7b6c5d4e3f2a1b0c"
import httpx

AUTH_TOKEN = "ak_01J8K92M4:as_9f8a7b6c5d4e3f2a1b0c"

response = httpx.get(
    "https://api.abelo.ai/v1/profiles",
    headers={
        "Authorization": f"Bearer {AUTH_TOKEN}"
    }
)
print(response.status_code, response.json())
const AUTH_TOKEN = "ak_01J8K92M4:as_9f8a7b6c5d4e3f2a1b0c";

const response = await fetch("https://api.abelo.ai/v1/profiles", {
  headers: {
    "Authorization": `Bearer ${AUTH_TOKEN}`
  }
});

const data = await response.json();
console.log(response.status, data);

Method 2: HMAC-SHA256 Request Signing

For enhanced security, HMAC-SHA256 Request Signing verifies message integrity and prevents replay attacks by signing the timestamp, HTTP method, request path, and request body with your hmac_secret.

Required Headers

Header Description Example
X-Abelo-Key-Id The public Key ID ak_01J8K92M4
X-Abelo-Timestamp Unix timestamp in seconds (UTC) of request. Used for defense against intercepted replayed requests. 1724164200
X-Abelo-Signature Hex-encoded HMAC-SHA256 signature sha256=a1b2c3d4...

Signature Canonical String

The signature is computed over the following payload:

{timestamp}.{HTTP_METHOD}.{PATH}.{RAW_REQUEST_BODY}

For requests without a body (e.g. GET or DELETE), use an empty string for {RAW_REQUEST_BODY}:
{timestamp}.GET./v1/profiles.

Python Signing Implementation

import hashlib
import hmac
import time
import httpx
from typing import Any, Dict, Optional
import json

KEY_ID = "ak_01J8K92M4"
HMAC_SECRET = "hs_9f8a7b6c5d4e3f2a1b0c"
API_URL = "https://api.abelo.ai"


def generate_auth_headers(method: str, path: str, body_bytes: bytes) -> dict[str, str]:
    timestamp = str(int(time.time()))
    body_hash = hashlib.sha256(body_bytes).hexdigest()

    sig_string = f"{timestamp}\n{method.upper()}\n{path}\n{body_hash}"

    signature = hmac.new(
        key=HMAC_SECRET.encode("utf-8"),
        msg=sig_string.encode("utf-8"),
        digestmod=hashlib.sha256
    ).hexdigest()

    return {
        "X-Abelo-Key-Id": KEY_ID,
        "X-Abelo-Timestamp": timestamp,
        "X-Abelo-Signature": signature,
        "Content-Type": "application/json"
    }

def dispatch(method: str, path: str, payload: Optional[Dict[str, Any]] = None) -> httpx.Response:
    url = f"{API_URL}{path}"

    # Prepare the exact bytes that will be sent over the wire.
    # Using separators=(',', ':') removes whitespace, making the JSON compact
    # and preventing hash mismatches caused by different JSON formatters.
    if payload is not None and method.upper() not in ["GET", "HEAD"]:
        body_bytes = json.dumps(payload, separators=(',', ':')).encode("utf-8")
    else:
        body_bytes = b""

    headers = generate_auth_headers(method, path, body_bytes)

    with httpx.Client() as _client:
        request = _client.build_request(
            method=method,
            url=url,
            content=body_bytes if body_bytes else None,
            headers=headers
        )
        return _client.send(request)



print("Dispatching GET...")
get_response = dispatch(
    method="GET",
    path="/v1/healthcheck"
)
print(f"GET Response Status: {get_response.status_code}")
print(f"GET Response Body: {get_response.json()}")


print("Dispatching POST...")
post_response = dispatch(
    method="POST",
    path="/v1/messages/send",
    payload={
        "campaign_id":"cmp_01J8K92M4",
        "recipient": {
            "type":"phone_number",
            "value":"+306912345678"
        }
    }
)
print(f"POST Response Status: {post_response.status_code}")
print(f"POST Response Body: {post_response.json()}\n")

print(post_response.status_code, post_response.json())

Replay Attack Window

The timestamp header must be within 60 seconds of server time. Requests received outside this validity window return 401 Unauthorized (Request expired).


3. API Scopes & Access Control

Each API key is restricted to the specific scopes assigned to it:

Scope Description Granted Endpoints
messages:send Trigger API campaigns POST /v1/messages/send
profiles:read Query and retrieve profiles GET /v1/profiles
GET /v1/profiles/{public_id}
profiles:write Create, update, and delete profiles POST /v1/profiles
PATCH /v1/profiles/{public_id}
DELETE /v1/profiles/{public_id}
webhooks:manage Manage webhooks GET /v1/webhooks
POST /v1/webhooks
PATCH /v1/webhooks/{id}
DELETE /v1/webhooks/{id}
POST /v1/webhooks/{id}/rotate-secret
POST /v1/webhooks/test