Skip to content

Authentication

The EngageHQ Public API uses JWT-based authentication with organization context. This guide explains how authentication works and how to properly configure your requests.

Authentication Flow

┌─────────────┐     ┌─────────────────┐     ┌─────────────┐
│  Your App   │────▶│ Identity Service│────▶│ EngageHQ API│
│             │◀────│   (JWT Token)   │     │             │
└─────────────┘     └─────────────────┘     └─────────────┘
  1. Your application authenticates with the ShopHero Identity service
  2. Identity service returns a JWT token
  3. Include the token in all EngageHQ API requests

Required Headers

Authorization Header

Every request must include the Authorization header with a valid JWT token:

http
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...

X-Organization-Context Header

The X-Organization-Context header identifies which organization or location to return content for:

http
X-Organization-Context: org_00000a1B2c3D4e5

Required for CDN endpoint

When using the CDN endpoint (cdn.engagehq.*), always include X-Organization-Context. This header is used as the CDN cache key for per-location isolation — without it, caching won't work correctly.

The value can be:

  • Your JWT's organization (the retailer/parent org)
  • A descendant location (a store within your organization's hierarchy)

When a descendant location is provided, the API validates that it belongs to your JWT's organization hierarchy and scopes all responses to that location. This also enables content distribution filtering — content distributed from parent organizations will be included.

location_id Query Parameter

You can also target a specific location using the location_id query parameter:

http
GET /api/v1/public/content?location_id=org_00000x1Y2z3W4v5

When both X-Organization-Context header and location_id query parameter are provided, the query parameter takes precedence as an explicit override.

Organization Resolution (Precedence)

  1. location_id query parameter (explicit override)
  2. X-Organization-Context header (recommended for CDN caching)
  3. JWT token organization claim (automatic fallback)

Example Request

javascript
const API_BASE = 'https://cdn.engagehq.retailsuccessplatform.com/api/v1/public';

// Fetch content for a specific store location
async function fetchContent(token, locationId) {
  const response = await fetch(`${API_BASE}/content`, {
    headers: {
      'Authorization': `Bearer ${token}`,
      'X-Organization-Context': locationId,  // Store's org hashkey
      'Content-Type': 'application/json',
    },
  });
  return response.json();
}

// Or use location_id query parameter for explicit targeting
async function fetchContentForLocation(token, locationId) {
  const response = await fetch(`${API_BASE}/content?location_id=${locationId}`, {
    headers: {
      'Authorization': `Bearer ${token}`,
      'Content-Type': 'application/json',
    },
  });
  return response.json();
}

Token Claims

Your JWT token includes claims that the API uses for authorization:

ClaimDescription
user_idYour user identifier
organization_idYour active organization
organizationsArray of accessible organization IDs
rolesYour permission roles
expToken expiration timestamp

Error Responses

401 Unauthorized

Returned when the token is missing, invalid, or expired:

json
{
  "success": false,
  "message": "Unauthorized",
  "errors": null
}

Solutions:

  • Check that the Authorization header is present
  • Verify the token hasn't expired
  • Ensure the token format is correct (Bearer <token>)

403 Forbidden

Returned when the token is valid but you don't have access to the requested organization:

json
{
  "success": false,
  "message": "Access denied to organization",
  "errors": null
}

Solutions:

  • Verify the organization ID is correct
  • Confirm your account has access to the organization
  • Contact your administrator for access

403 Location Not Accessible

Returned when the X-Organization-Context header or location_id parameter specifies an organization that is not accessible from your JWT token:

json
{
  "success": false,
  "message": "Location not accessible from token organization.",
  "error": "location_not_accessible"
}

Solutions:

  • Ensure the location is a descendant of your JWT's organization (e.g., a store within your retailer)
  • Verify the organization hashkey is correct
  • Contact your administrator if you need access to additional locations

400 Organization Context Required

Returned when no organization context can be determined:

json
{
  "success": false,
  "message": "Organization context required",
  "errors": null
}

Solutions:

  • Pass the X-Organization-Context header (recommended)
  • Ensure your token includes an active organization claim

Token Refresh

JWT tokens expire after a set period (typically 1 hour). Implement token refresh in your application:

javascript
class ApiClient {
  constructor(identityService, organizationId) {
    this.identityService = identityService;
    this.organizationId = organizationId;
    this.token = null;
    this.tokenExpiry = null;
  }

  async getToken() {
    // Refresh if token is expired or will expire in 5 minutes
    const bufferMs = 5 * 60 * 1000;
    if (!this.token || Date.now() > this.tokenExpiry - bufferMs) {
      const { token, expires_at } = await this.identityService.refresh();
      this.token = token;
      this.tokenExpiry = new Date(expires_at).getTime();
    }
    return this.token;
  }

  async fetch(endpoint) {
    const token = await this.getToken();
    return fetch(`${API_BASE}${endpoint}`, {
      headers: {
        'Authorization': `Bearer ${token}`,
        'X-Organization-Context': this.organizationId,
      },
    });
  }
}

Security Best Practices

Never expose tokens in client-side code

Store tokens securely and never include them in URLs or client-side JavaScript that could be inspected.

  1. Use HTTPS only - Never send tokens over unencrypted connections
  2. Store tokens securely - Use httpOnly cookies or secure storage
  3. Implement token refresh - Don't let tokens expire mid-session
  4. Validate on the server - When building a backend, validate tokens server-side
  5. Handle errors gracefully - Redirect to login on 401 responses

Next Steps


Changelog
DateChange
2026-02-23Updated for CloudFront CDN and Lambda@Edge delivery.
2026-01-28Expanded documentation.
2026-01-15Initial publication.

EngageHQ Public Content Delivery API