Notification Service API Integration Guide

Last updated: December 2025

Overview

The Notification Service is part of your API suite subscription. This service allows you to send, manage, and retrieve notifications for your users. To access the service, you need an API Key that was provided when you subscribed to the API suite.

This guide explains how to integrate the Notification Service into your application, covering both backend and frontend integration scenarios.

Authentication Methods

The Notification Service supports two authentication methods:

  1. API Key Authentication – For backend-to-backend communication
  2. JWT Token Authentication – For frontend-to-backend communication

Backend Integration (Using API Key)

When making API calls from your backend server, you can authenticate using your API suite API Key. Simply include the API Key in the X-API-Key header of your requests.

Example: Creating a Notification (Backend)

// Node.js/Express example
const axios = require('axios');

async function createNotification(userId, notificationData) {
  try {
    const response = await axios.post(
      'https://notifications-api.agglestone.net/api/Notifications',
      {
        userId: userId,
        subject: notificationData.subject,
        message: notificationData.message,
        type: 'SystemAnnouncement',
        priority: 'Normal',
        email: notificationData.email
      },
      {
        headers: {
          'X-API-Key': 'your-api-suite-api-key-here',
          'Content-Type': 'application/json'
        }
      }
    );
    
    console.log('Notification created:', response.data);
    return response.data;
  } catch (error) {
    console.error('Error creating notification:', error.response?.data || error.message);
    throw error;
  }
}
# Python example
import requests

def create_notification(user_id, notification_data):
    url = 'https://notifications-api.agglestone.net/api/Notifications'
    headers = {
        'X-API-Key': 'your-api-suite-api-key-here',
        'Content-Type': 'application/json'
    }
    payload = {
        'userId': user_id,
        'subject': notification_data['subject'],
        'message': notification_data['message'],
        'type': 'SystemAnnouncement',
        'priority': 'Normal',
        'email': notification_data.get('email')
    }
    
    response = requests.post(url, json=payload, headers=headers)
    response.raise_for_status()
    return response.json()
// C# example
using System.Net.Http;
using System.Text;
using System.Text.Json;

public async Task<Notification> CreateNotificationAsync(string userId, NotificationData data)
{
    var client = new HttpClient();
    client.DefaultRequestHeaders.Add("X-API-Key", "your-api-suite-api-key-here");
    
    var payload = new
    {
        userId = userId,
        subject = data.Subject,
        message = data.Message,
        type = "SystemAnnouncement",
        priority = "Normal",
        email = data.Email
    };
    
    var json = JsonSerializer.Serialize(payload);
    var content = new StringContent(json, Encoding.UTF8, "application/json");
    
    var response = await client.PostAsync(
        "https://notifications-api.agglestone.net/api/Notifications",
        content
    );
    
    response.EnsureSuccessStatusCode();
    var responseBody = await response.Content.ReadAsStringAsync();
    return JsonSerializer.Deserialize<Notification>(responseBody);
}

Example: Retrieving User Notifications (Backend)

// Node.js/Express example
const axios = require('axios');

async function getUserNotifications(userId, pageNumber = 1, pageSize = 20) {
  try {
    const response = await axios.get(
      'https://notifications-api.agglestone.net/api/Notifications',
      {
        params: {
          pageNumber: pageNumber,
          pageSize: pageSize
        },
        headers: {
          'X-API-Key': 'your-api-suite-api-key-here'
        }
      }
    );
    
    // Response structure: { data: [...], pageNumber: 1, pageSize: 20, totalRecords: 100, totalPages: 5 }
    return response.data;
  } catch (error) {
    console.error('Error fetching notifications:', error.response?.data || error.message);
    throw error;
  }
}
# Python example
import requests

def get_user_notifications(user_id, page_number=1, page_size=20):
    url = 'https://notifications-api.agglestone.net/api/Notifications'
    headers = {
        'X-API-Key': 'your-api-suite-api-key-here'
    }
    params = {
        'pageNumber': page_number,
        'pageSize': page_size
    }
    
    response = requests.get(url, headers=headers, params=params)
    response.raise_for_status()
    
    # Response structure: { "data": [...], "pageNumber": 1, "pageSize": 20, "totalRecords": 100, "totalPages": 5 }
    return response.json()
// C# example
using System.Net.Http;
using System.Text.Json;

public async Task<PagedResponse<Notification>> GetUserNotificationsAsync(
    string userId, 
    int pageNumber = 1, 
    int pageSize = 20)
{
    var client = new HttpClient();
    client.DefaultRequestHeaders.Add("X-API-Key", "your-api-suite-api-key-here");
    
    var url = $"https://notifications-api.agglestone.net/api/Notifications" +
              $"?pageNumber={pageNumber}&pageSize={pageSize}";
    
    var response = await client.GetAsync(url);
    response.EnsureSuccessStatusCode();
    
    var responseBody = await response.Content.ReadAsStringAsync();
    // Response structure: { "data": [...], "pageNumber": 1, "pageSize": 20, "totalRecords": 100, "totalPages": 5 }
    return JsonSerializer.Deserialize<PagedResponse<Notification>>(responseBody);
}

Frontend Integration (Using JWT Token)

For frontend applications, you’ll need to use JWT tokens for authentication. The workflow is:

  1. Your backend exchanges the API Key for a JWT token using the token exchange endpoint
  2. Your backend sends the JWT token to your frontend
  3. Your frontend includes the JWT token as a Bearer token in API requests

Step 1: Exchange API Key for JWT Token

Your backend should call the token exchange endpoint to get a JWT token for a specific user:

// Backend: Exchange API Key for JWT Token (Node.js/Express)
const axios = require('axios');

async function exchangeTokenForJWT(userId) {
  try {
    const response = await axios.post(
      'https://notifications-api.agglestone.net/api/Auth/token-exchange',
      {
        userId: userId
      },
      {
        headers: {
          'X-API-Key': 'your-api-suite-api-key-here',
          'Content-Type': 'application/json'
        }
      }
    );
    
    // Response format:
    // {
    //   "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    //   "tokenType": "Bearer",
    //   "expiresIn": 43200
    // }
    
    return response.data.token;
  } catch (error) {
    console.error('Error exchanging token:', error.response?.data || error.message);
    throw error;
  }
}
# Backend: Exchange API Key for JWT Token (Python)
import requests

def exchange_token_for_jwt(user_id):
    url = 'https://notifications-api.agglestone.net/api/Auth/token-exchange'
    headers = {
        'X-API-Key': 'your-api-suite-api-key-here',
        'Content-Type': 'application/json'
    }
    payload = {
        'userId': user_id
    }
    
    response = requests.post(url, json=payload, headers=headers)
    response.raise_for_status()
    data = response.json()
    return data['token']  # Returns the JWT token
// Backend: Exchange API Key for JWT Token (C#)
using System.Net.Http;
using System.Text;
using System.Text.Json;

public async Task<string> ExchangeTokenForJWTAsync(string userId)
{
    var client = new HttpClient();
    client.DefaultRequestHeaders.Add("X-API-Key", "your-api-suite-api-key-here");
    
    var payload = new
    {
        userId = userId
    };
    
    var json = JsonSerializer.Serialize(payload);
    var content = new StringContent(json, Encoding.UTF8, "application/json");
    
    var response = await client.PostAsync(
        "https://notifications-api.agglestone.net/api/Auth/token-exchange",
        content
    );
    
    response.EnsureSuccessStatusCode();
    var responseBody = await response.Content.ReadAsStringAsync();
    var tokenResponse = JsonSerializer.Deserialize<JsonElement>(responseBody);
    
    // Response format:
    // {
    //   "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    //   "tokenType": "Bearer",
    //   "expiresIn": 43200
    // }
    
    return tokenResponse.GetProperty("token").GetString();
}

Step 2: Send JWT Token to Frontend

After obtaining the JWT token, your backend should send it to your frontend application (typically during user login or session initialization):

// Backend endpoint example (Express/Node.js)
const express = require('express');
const app = express();

app.post('/api/user/login', async (req, res) => {
  // ... authenticate user ...
  const userId = req.user.id;
  
  // Exchange API Key for JWT token
  const jwtToken = await exchangeTokenForJWT(userId);
  
  // Send token to frontend
  res.json({
    user: req.user,
    accessToken: jwtToken,
    tokenType: 'Bearer'
  });
});
# Backend endpoint example (Flask/Python)
from flask import Flask, request, jsonify
import requests

app = Flask(__name__)

@app.route('/api/user/login', methods=['POST'])
def login():
    # ... authenticate user ...
    user_id = current_user.id
    
    # Exchange API Key for JWT token
    jwt_token = exchange_token_for_jwt(user_id)
    
    # Send token to frontend
    return jsonify({
        'user': current_user.to_dict(),
        'accessToken': jwt_token,
        'tokenType': 'Bearer'
    })
// Backend endpoint example (ASP.NET Core/C#)
using Microsoft.AspNetCore.Mvc;

[ApiController]
[Route("api/[controller]")]
public class UserController : ControllerBase
{
    [HttpPost("login")]
    public async Task<IActionResult> Login([FromBody] LoginRequest request)
    {
        // ... authenticate user ...
        var userId = authenticatedUser.Id;
        
        // Exchange API Key for JWT token
        var jwtToken = await ExchangeTokenForJWTAsync(userId);
        
        // Send token to frontend
        return Ok(new
        {
            user = authenticatedUser,
            accessToken = jwtToken,
            tokenType = "Bearer"
        });
    }
}

Step 3: Use JWT Token in Frontend Requests

Your frontend application should include the JWT token in the Authorization header as a Bearer token:

// Frontend: React example
import axios from 'axios';

// Set up axios interceptor to include JWT token
axios.interceptors.request.use((config) => {
  const token = localStorage.getItem('accessToken');
  if (token) {
    config.headers.Authorization = Bearer ${token};
  }
  return config;
});

// Fetch user notifications
async function fetchUserNotifications(pageNumber = 1, pageSize = 20) {
  try {
    const response = await axios.get(
      'https://notifications-api.agglestone.net/api/Notifications',
      {
        params: {
          pageNumber: pageNumber,
          pageSize: pageSize
        }
      }
    );
    
    // Response structure: { data: [...], pageNumber: 1, pageSize: 20, totalRecords: 100, totalPages: 5 }
    // Access notifications: response.data.data
    // Access pagination info: response.data.pageNumber, response.data.totalPages, etc.
    return response.data;
  } catch (error) {
    console.error('Error fetching notifications:', error.response?.data || error.message);
    throw error;
  }
}

// Mark notification as read
async function markNotificationAsRead(notificationId) {
  try {
    await axios.put(
      https://notifications-api.agglestone.net/api/Notifications/${notificationId}/read,
      {},
      {
        headers: {
          'Authorization': Bearer ${localStorage.getItem('accessToken')}
        }
      }
    );
  } catch (error) {
    console.error('Error marking notification as read:', error.response?.data || error.message);
    throw error;
  }
}
// Frontend: TypeScript/React example
import axios, { AxiosInstance } from 'axios';

class NotificationService {
  private api: AxiosInstance;
  
  constructor(baseURL: string) {
    this.api = axios.create({
      baseURL: baseURL,
      headers: {
        'Content-Type': 'application/json'
      }
    });
    
    // Add token to all requests
    this.api.interceptors.request.use((config) => {
      const token = this.getToken();
      if (token) {
        config.headers.Authorization = Bearer ${token};
      }
      return config;
    });
  }
  
  private getToken(): string | null {
    return localStorage.getItem('accessToken');
  }
  
  async getNotifications(pageNumber: number = 1, pageSize: number = 20) {
    const response = await this.api.get('/api/Notifications', {
      params: { pageNumber, pageSize }
    });
    return response.data;
  }
  
  async getNotification(id: string) {
    const response = await this.api.get(/api/Notifications/${id});
    return response.data;
  }
  
  async markAsRead(notificationId: string) {
    await this.api.put(/api/Notifications/${notificationId}/read);
  }
  
  async deleteNotification(notificationId: string) {
    await this.api.delete(/api/Notifications/${notificationId});
  }
}

// Usage
const notificationService = new NotificationService('https://notifications-api.agglestone.net');

Available API Endpoints

Notifications

  • GET /api/Notifications – Get paginated list of notifications for the authenticated user

– Query parameters:
pageNumber (optional, default: 1)
pageSize (optional, default: 10, max: 50)
filter (optional, string)
sortField (optional, string)
sortOrder (optional, “asc” or “desc”, default: “asc”)

  • GET /api/Notifications/{id} – Get a specific notification by ID

– Returns 403 Forbidden if the notification does not belong to the authenticated user

  • GET /api/Notifications/{id}/content – Get notification content in different formats

– Query parameter: format (options: html, text, summary, full, default: full)
– Returns 403 Forbidden if the notification does not belong to the authenticated user

  • POST /api/Notifications – Create a new notification

– Request body: CreateNotificationRequest (see below)

  • PUT /api/Notifications/{notificationId}/read – Mark a notification as read

– Returns 404 Not Found if the notification does not exist or does not belong to the authenticated user

  • DELETE /api/Notifications/{notificationId} – Delete a notification

– Returns 404 Not Found if the notification does not exist or does not belong to the authenticated user

  • DELETE /api/Notifications/users/{userId} – Delete all communication data for a user (notifications, email records, notification groups)
  • PUT /api/Notifications/users/{oldUserId}/update-id – Update user ID in all communication records

– Request body: UpdateUserIdRequest (see below)
– Used when converting temp candidate IDs to full user IDs

Analytics

  • GET /api/Notifications/analytics/notifications – Get notification statistics

– Query parameters: fromDate (optional, ISO 8601 datetime), toDate (optional, ISO 8601 datetime)
– Returns: Dictionary of notification statistics

  • GET /api/Notifications/analytics/failed-notifications – Get failed notifications

– Query parameters: limit (optional, default: 100)
– Returns: List of failed notifications

  • GET /api/Notifications/analytics/user-notification-count – Get user notification count for a period

– Query parameters: fromDate (optional, ISO 8601 datetime), toDate (optional, ISO 8601 datetime)
– Returns: Integer count of notifications

Test Operations (Development Only)

  • POST /api/Notifications/test/send-email – Send a test email for verification

– Request body: CreateNotificationRequest (see below)
– Note: All emails are redirected to noreply@agglestone.com for testing

Authentication

  • POST /api/Auth/token-exchange – Exchange API Key for JWT token

– Headers: X-API-Key (your API suite API Key)
– Request body: { "userId": "user-id-here" }
– Response: { "token": "...", "tokenType": "Bearer", "expiresIn": 43200 }

Request/Response Models

CreateNotificationRequest

{
  "userId": "string (optional - defaults to authenticated user)",
  "clientId": "string (optional - automatically set from authentication)",
  "subject": "string (required)",
  "message": "string (required)",
  "type": "UserInvite | RegistrationWelcome | InactivityReminder | SystemAnnouncement | PasswordReset | AccountVerification",
  "priority": "Low | Normal | High | Urgent",
  "email": "string (optional)",
  "htmlContent": "string (optional)",
  "summary": "string (optional)",
  "scheduledFor": "ISO 8601 datetime string (optional)",
  "metadata": { "key": "value" } (optional)
}

Notification Response

{
  "id": "string",
  "objectType": "Notification",
  "userId": "string",
  "clientId": "string",
  "subject": "string",
  "message": "string",
  "htmlContent": "string",
  "summary": "string",
  "type": "UserInvite | RegistrationWelcome | InactivityReminder | SystemAnnouncement | PasswordReset | AccountVerification",
  "priority": "Low | Normal | High | Urgent",
  "status": "Pending | Scheduled | Sent | Read | Failed | Cancelled | Grouped",
  "createdAt": "ISO 8601 datetime string",
  "scheduledFor": "ISO 8601 datetime string (nullable)",
  "sentAt": "ISO 8601 datetime string (nullable)",
  "readAt": "ISO 8601 datetime string (nullable)",
  "metadata": { "key": "value" },
  "groupId": "string (empty if not grouped)",
  "email": "string",
  "retryCount": 0,
  "errorMessage": "string (empty if no errors)"
}

Paginated Response

{
  "data": [ / array of Notification objects / ],
  "pageNumber": 1,
  "pageSize": 20,
  "totalRecords": 100,
  "totalPages": 5
}

UpdateUserIdRequest

{
  "newUserId": "string (required)"
}

Error Handling

The API returns standard HTTP status codes:

  • 200 OK – Request successful
  • 201 Created – Resource created successfully
  • 400 Bad Request – Invalid request data
  • 401 Unauthorized – Missing or invalid authentication
  • 403 Forbidden – Access denied
  • 404 Not Found – Resource not found
  • 500 Internal Server Error – Server error

Error responses include a message in the response body:

{
  "message": "Error description"
}

For validation errors (400 Bad Request), the response includes detailed field-level errors:

{
  "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
  "title": "Validation Error",
  "status": 400,
  "detail": "Field 'subject': The subject field is required.",
  "errors": {
    "subject": ["The subject field is required."],
    "message": ["The message field is required."]
  }
}

Token Expiration

JWT tokens expire after 12 hours (configurable). Your frontend should:

  1. Monitor token expiration
  2. Request a new token from your backend before expiration
  3. Handle 401 Unauthorized responses by refreshing the token

Example token refresh logic:

// Frontend: Handle token refresh
axios.interceptors.response.use(
  (response) => response,
  async (error) => {
    if (error.response?.status === 401) {
      // Token expired or invalid
      const newToken = await refreshToken(); // Call your backend to get new token
      if (newToken) {
        localStorage.setItem('accessToken', newToken);
        // Retry the original request
        error.config.headers.Authorization = Bearer ${newToken};
        return axios.request(error.config);
      }
    }
    return Promise.reject(error);
  }
);

Configuration Options

When setting up the Notification Service, you may need to configure the following:

  • API Suite API Key – Your subscription API Key (required for all requests)
  • SendGrid API Key – Used by the service for sending emails (configured on the server side, not required in your integration)

The base URL for the API is: https://notifications-api.agglestone.net

For development/testing, you may use: https://localhost:7293 (if running locally)

Best Practices

  1. Store API Keys Securely: Never expose your API suite API Key in frontend code. Always keep it on your backend server.
  1. Token Management: Implement proper token refresh logic in your frontend to handle token expiration gracefully.
  1. Error Handling: Always handle API errors appropriately and provide user-friendly error messages. Check for validation errors in the errors object when receiving 400 Bad Request responses.
  1. Rate Limiting: Be mindful of API rate limits and implement appropriate retry logic with exponential backoff.
  1. User Context: When creating notifications from your backend, ensure the userId matches the intended recipient. If userId is not provided, it defaults to the authenticated user’s ID.
  1. HTTPS Only: Always use HTTPS when making API calls in production to protect sensitive data.
  1. Pagination: When fetching notifications, use appropriate page sizes (max 50) to optimize performance. Consider implementing infinite scroll or pagination controls in your UI.
  1. Notification Types: Use appropriate notification types to enable proper grouping and processing. Notifications of the same type for the same user may be grouped together.
  1. Scheduling: Use the scheduledFor field to schedule notifications for future delivery. Ensure the datetime is in ISO 8601 format and in UTC.
  1. Metadata: Use the metadata field to store additional custom data with notifications. This can be useful for tracking or custom processing logic.

Support

For API support or questions, please contact: support@agglestone.com

Additional Resources

  • Swagger: https://notifications-service-g9dydncmdghtc9bk.newzealandnorth-01.azurewebsites.net/swagger/v1/swagger.json
  • API Documentation: Available at /swagger endpoint when the service is running
  • For testing token generation, see HOW_TO_USE_GENERATE_TEST_TOKEN.md
  • For detailed JWT authentication information, see JWT_AUTHENTICATION_GUIDE.md