Authentication


Authentication

NotifyHub uses JWT (JSON Web Token) authentication to secure all API requests. This guide explains how authentication works and how to manage your tokens.


Overview

Every request to the NotifyHub API (except signup and login) requires a valid JWT token in the Authorization header.

Authentication Flow:

  1. Sign up or log in to receive a JWT token
  2. Include the token in the Authorization header for all requests
  3. Token expires after 24 hours (configurable)
  4. Refresh by logging in again

Sign Up

Create a new organization and get your first admin account.

Endpoint

POST https://api.notifyhub.com/auth/signup

Request

Headers:

Content-Type: application/json

Body:

{
  "firstName": "John",
  "lastName": "Doe",
  "email": "john@yourcompany.com",
  "password": "SecurePassword123!",
  "countryCode": "+254",
  "phoneNumber": "712345678",
  "companyName": "Your Company Ltd",
  "sector": "Technology",
  "country": "Kenya",
  "role": "admin"
}

Field Descriptions:

| Field | Type | Required | Description | |-------|------|----------|-------------| | firstName | string | Yes | User's first name | | lastName | string | Yes | User's last name | | email | string | Yes | Unique email address (used for login) | | password | string | Yes | Strong password (min 8 characters) | | countryCode | string | Yes | Phone country code (e.g., "+254") | | phoneNumber | string | Yes | Phone number without country code | | companyName | string | Yes | Organization/company name | | sector | string | Yes | Industry sector | | country | string | Yes | Country of operation | | role | string | No | User role (defaults to "admin") |

Response

Success (201 Created):

{
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiI2OTVjYzNiMDIxMjc0MGJkMWRiYTQwZDQiLCJvcmdJZCI6IjY5NWNjM2IwMjEyNzQwYmQxZGJhNDBkMiIsInJvbGUiOiJhZG1pbiIsImlhdCI6MTY0MTIzNDU2NywiZXhwIjoxNjQxMzIwOTY3fQ.abc123...",
  "user": {
    "_id": "695cc3b0212740bd1dba40d4",
    "firstName": "John",
    "lastName": "Doe",
    "email": "john@yourcompany.com",
    "countryCode": "+254",
    "phoneNumber": "712345678",
    "role": "admin",
    "organization": "695cc3b0212740bd1dba40d2",
    "isActive": true,
    "createdAt": "2026-01-06T08:11:28.334Z",
    "updatedAt": "2026-01-06T08:11:28.334Z"
  }
}

Error (400 Bad Request):

{
  "statusCode": 400,
  "message": "Email already exists",
  "error": "Bad Request"
}

Example

cURL:

curl -X POST https://api.notifyhub.com/auth/signup \
  -H "Content-Type: application/json" \
  -d '{
    "firstName": "John",
    "lastName": "Doe",
    "email": "john@yourcompany.com",
    "password": "SecurePassword123!",
    "countryCode": "+254",
    "phoneNumber": "712345678",
    "companyName": "Your Company Ltd",
    "sector": "Technology",
    "country": "Kenya"
  }'

JavaScript:

const response = await fetch('https://api.notifyhub.com/auth/signup', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    firstName: 'John',
    lastName: 'Doe',
    email: 'john@yourcompany.com',
    password: 'SecurePassword123!',
    countryCode: '+254',
    phoneNumber: '712345678',
    companyName: 'Your Company Ltd',
    sector: 'Technology',
    country: 'Kenya'
  })
});
 
const { token, user } = await response.json();

Login

Authenticate with existing credentials to get a new JWT token.

Endpoint

POST https://api.notifyhub.com/auth/login

Request

Headers:

Content-Type: application/json

Body:

{
  "email": "john@yourcompany.com",
  "password": "SecurePassword123!"
}

Field Descriptions:

| Field | Type | Required | Description | |-------|------|----------|-------------| | email | string | Yes | Your registered email address | | password | string | Yes | Your account password |

Response

Success (200 OK):

{
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "user": {
    "_id": "695cc3b0212740bd1dba40d4",
    "firstName": "John",
    "lastName": "Doe",
    "email": "john@yourcompany.com",
    "role": "admin",
    "organization": "695cc3b0212740bd1dba40d2",
    "isActive": true
  }
}

Error (401 Unauthorized):

{
  "statusCode": 401,
  "message": "Invalid credentials",
  "error": "Unauthorized"
}

Example

cURL:

curl -X POST https://api.notifyhub.com/auth/login \
  -H "Content-Type: application/json" \
  -d '{
    "email": "john@yourcompany.com",
    "password": "SecurePassword123!"
  }'

JavaScript:

const response = await fetch('https://api.notifyhub.com/auth/login', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    email: 'john@yourcompany.com',
    password: 'SecurePassword123!'
  })
});
 
const { token, user } = await response.json();
// Store token securely for subsequent requests
localStorage.setItem('notifyhub_token', token);

API Key Authentication

For server-to-server integrations and external developers, NotifyHub supports API Key authentication. API Keys do not expire unless revoked and are ideal for automated systems.

Generating a Key

API Keys can be generated from the Developers > API Keys section in your dashboard. Ensure you copy the key immediately, as it will only be shown once.

For security, developers should store API details in their environment variables (.env):

Unified_API_URL=http://localhost:3040
Unified_API_KEY=your_generted_api_key

Usage

You can authenticate using your API Key in two ways:

Add the UNIFIED-API-Key header to your request.

UNIFIED-API-Key: YOUR_API_KEY_HERE

2. Query Parameter

Append the key to your request URL using the apikey parameter.

http://localhost:3040/notifications/send?apikey=YOUR_API_KEY_HERE

Security Considerations

  • Keep it secret: Treat your API Key like a password. Never share it or commit it to version control.
  • Server-side only: Use API Keys only in server-to-server calls. Never expose them in client-side code (browsers/mobile apps) where they can be easily extracted.
  • Revoke if compromised: If a key is exposed, revoke it immediately in the dashboard and generate a new one.

Using Your Token

Include the JWT token in the Authorization header for all authenticated requests.

Header Format

Authorization: Bearer YOUR_TOKEN_HERE

Example Request

cURL:

curl -X GET https://api.notifyhub.com/users/me \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."

JavaScript:

const token = localStorage.getItem('notifyhub_token');
 
const response = await fetch('https://api.notifyhub.com/users/me', {
  headers: {
    'Authorization': `Bearer ${token}`
  }
});
 
const user = await response.json();

Python:

import requests
 
token = 'YOUR_TOKEN_HERE'
headers = {'Authorization': f'Bearer {token}'}
 
response = requests.get(
    'https://api.notifyhub.com/users/me',
    headers=headers
)
 
user = response.json()

Token Structure

JWT tokens contain encoded information about the authenticated user:

Decoded Payload:

{
  "userId": "695cc3b0212740bd1dba40d4",
  "orgId": "695cc3b0212740bd1dba40d2",
  "role": "admin",
  "iat": 1641234567,
  "exp": 1641320967
}

Fields:

  • userId: Your unique user ID
  • orgId: Your organization ID (all operations are scoped to this)
  • role: Your role (admin or user)
  • iat: Issued at timestamp
  • exp: Expiration timestamp

Security Note: The token payload is signed but not encrypted. Never include sensitive data in custom claims.


Token Expiration

Tokens expire after 24 hours by default.

Handling Expiration

When a token expires, you'll receive a 401 Unauthorized response:

{
  "statusCode": 401,
  "message": "Token expired",
  "error": "Unauthorized"
}

Solution: Log in again to get a new token.

Best Practices

  1. Store tokens securely

    • Never commit tokens to version control
    • Use environment variables or secure credential stores
    • In browsers: Use secure, httpOnly cookies (not localStorage for production)
  2. Implement token refresh logic

    async function apiRequest(url, options = {}) {
      let token = getStoredToken();
      
      let response = await fetch(url, {
        ...options,
        headers: {
          ...options.headers,
          'Authorization': `Bearer ${token}`
        }
      });
      
      // If token expired, refresh and retry
      if (response.status === 401) {
        token = await refreshToken(); // Login again
        response = await fetch(url, {
          ...options,
          headers: {
            ...options.headers,
            'Authorization': `Bearer ${token}`
          }
        });
      }
      
      return response;
    }
  3. Monitor token expiration

    • Decode JWT to check exp timestamp
    • Refresh proactively before expiration
    function isTokenExpired(token) {
      const payload = JSON.parse(atob(token.split('.')[1]));
      return Date.now() >= payload.exp * 1000;
    }

Roles & Permissions

NotifyHub has two user roles:

Admin

Permissions:

  • ✅ All messaging operations (SMS, Email, WhatsApp)
  • ✅ Create, update, delete contacts and groups
  • ✅ Create, manage, launch campaigns
  • ✅ View message logs and analytics
  • ✅ Manage team members (create, update, deactivate users)
  • ✅ Update organization credentials
  • ✅ Access all endpoints

User

Permissions:

  • ✅ Send messages (SMS, Email, WhatsApp)
  • ✅ View contacts and groups
  • ✅ View message logs
  • ✅ View own profile
  • ❌ Cannot create/delete contacts or groups
  • ❌ Cannot manage campaigns
  • ❌ Cannot manage team members
  • ❌ Cannot update organization credentials

Role-Based Access Example:

// This works for both admin and user
await sendSMS('+254712345678', 'Hello!');
 
// This requires admin role
await createContact({name: 'John', phone: '+254712345678'});
// User role will receive: 403 Forbidden

Common Authentication Errors

401 Unauthorized

Causes:

  • Missing Authorization header
  • Invalid or malformed token
  • Token expired
  • Token signature mismatch

Solution:

// Ensure token is included correctly
headers: {
  'Authorization': `Bearer ${token}`, // Note the space after Bearer
  'Content-Type': 'application/json'
}

403 Forbidden

Cause: Insufficient permissions (trying to access admin-only endpoint with user role)

Solution: Login with an admin account or request admin permissions from your organization admin.


Security Best Practices

  1. Use HTTPS only: Never send tokens over unencrypted connections
  2. Rotate tokens regularly: Log in periodically to get fresh tokens
  3. Implement token storage securely:
    • Backend: Environment variables or secret managers
    • Frontend: Secure cookies with httpOnly flag (not localStorage)
  4. Validate token on every request: Don't trust client-side expiration checks
  5. Monitor for suspicious activity: Track unusual login patterns
  6. Use strong passwords: Enforce minimum 8 characters with complexity
  7. Enable 2FA (coming soon): Additional security layer

Testing Authentication

Quick Test Script

// test-auth.js
const BASE_URL = 'https://api.notifyhub.com';
 
async function testAuth() {
  // 1. Login
  const loginResponse = await fetch(`${BASE_URL}/auth/login`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      email: 'your-email@example.com',
      password: 'your-password'
    })
  });
  
  const { token } = await loginResponse.json();
  console.log('Token received:', token.substring(0, 20) + '...');
  
  // 2. Test authenticated request
  const userResponse = await fetch(`${BASE_URL}/users/me`, {
    headers: { 'Authorization': `Bearer ${token}` }
  });
  
  const user = await userResponse.json();
  console.log('Authenticated user:', user.email);
  
  // 3. Test sending a message
  const messageResponse = await fetch(`${BASE_URL}/notifications/send`, {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${token}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      type: 'sms',
      to: '+254712345678',
      message: 'Auth test successful!'
    })
  });
  
  const result = await messageResponse.json();
  console.log('Message sent:', result);
}
 
testAuth().catch(console.error);

Next Steps: