Event Notification API
1. Key Components and Classes
1.1 OutboundNotificationHandler
The OutboundNotificationHandler
class is the main entry point for pushing outbound notifications. It:
- Retrieves entity-specific configuration using
getConfigForEntity(entityId)
. - Filters and iterates over configured endpoints (e.g.,
PushEndpoints
,FileUploadPushEndpoints
,InventoryPushEndpoints
, etc.). - Applies conditional checks (through
EndpointProcessor
) to determine if a given payload matches the endpoint’s criteria. - Sends the payload to the endpoint using the configured processor.
- Captures, logs, and returns success or failure details, including whether a retry can be attempted if a failure occurs.
The core method is pushOutbound(entityId, payload, op, endpointsCfg, timeout)
. When invoked:
- Loads the BNS configuration for the given
entityId
. - Identifies which set of endpoints should be used (e.g.,
PushEndpoints
,FileUploadPushEndpoints
, orInventoryPushEndpoints
). - Processes each endpoint:
- Skips if
Enabled
isfalse
. - Evaluates conditions (Expr-eval or JSONPath).
- Dispatches the payload if conditions pass.
- Returns either a
SuccessResponse
or aFailureResponse
.
- Skips if
- Aggregates all endpoint results into a single response.
Note: The handler can optionally apply a timebox (
timeout
) to each endpoint request, after which it throws aTimeoutError
. This is useful to avoid blocking calls.
1.2 Endpoint Processors
OutboundNotificationHandler
delegates actual HTTP (or other protocol) logic to various EndpointProcessor
implementations. Three are mentioned in the code:
- RestEndpointProcessor (Deprecated).
- OAuthEndpointProcessor (Deprecated).
- WebhookEndpointProcessor (handles general-purpose webhook calls, possibly with or without OAuth authentication).
This approach allows for modular extension—if you have new service types (e.g., gRPC, MQTT), you can implement a new EndpointProcessor
.
1.3 WebhookEndpointProcessor
The WebhookEndpointProcessor
handles any endpoints whose ServiceType
is "Webhook"
. Its key responsibilities are:
- Condition Checks: Uses either expr-eval or JSONPath-based logic to determine if the endpoint should be invoked.
- Authentication: If OAuth2 is configured, it obtains tokens from a cache (supplied in the constructor), or fetches new tokens as necessary.
- Request Dispatch: Substitutes variables in the request definition (
url
,headers
, etc.) using the provided payload. Then it sends the request withaxios
. - Response Handling: Returns a standardized
StandardResponse
containing status, body, and headers.
2. Configuration Structure
The configuration for BNS typically lives under something like bnsCfg.Modules.BNS
. Below is an example configuration snippet (from your code example) showing four main sub-keys that hold arrays of endpoints:
"BNS": {
"PushEndpoints": [
{
"Enabled": true,
"ServiceType": "Webhook",
"Request": {
"url": "https://so-dev.hostedservicepower.com/api/v1/work-order/webhook",
"headers": {
"Authorization": "--Redacted--"
}
},
"Condition": "$[?(@.WorkItemType==1)]"
},
{
"Enabled": true,
"ServiceType": "Webhook",
"Request": {
"url": "https://consumer-portal.hostedservicepower.com:9200/api/v1/webhook",
"headers": {
"Authorization": "--Redacted--"
}
},
"Condition": "$[?(@.WorkItemType==1)]"
},
{
"Enabled": true,
"ServiceType": "Webhook",
"Request": {
"url": "https://so-dev.hostedservicepower.com/api/v1/work-order/notification-webhook",
"headers": {
"Authorization": "--Redacted--"
}
},
"Condition": "$[?(@.OrderNum!=)]"
}
],
"FileUploadPushEndpoints": [
{
"Enabled": true,
"ServiceType": "Webhook",
"Request": {
"url": "https://8ea49172-5da9-4428-b117-282e67004502.mock.pstmn.io/v1/notification/file-upload"
},
"Authentication": {
"OAuth2": {
"ClientId": "--Redacted--",
"ClientSecret": "--Redacted--",
"TokenUrl": "https://mred-sp.eu.auth0.com/oauth/token",
"GrantType": "client_credentials",
"Supplements": {
"headers": {
"Content-Type": "application/json"
}
}
}
}
}
],
"InventoryPushEndpoints": [
{
"Enabled": true,
"ServiceType": "Webhook",
"Request": {
"url": "https://sb-dev.hostedservicepower.com/up/v5/bns/webhook",
"headers": {
"Authorization": "--Redacted--"
}
},
"Condition": ""
}
],
"Preference": {
"Email": {
"Provider": "Emarsys"
},
"SMS": {}
},
"SES": {}
}
Enabled
: Boolean indicating whether this endpoint is active.ServiceType
: The processor to use (e.g.,Webhook
,REST
,OAuthRest
).Request
:url
: The URL where the payload will be sent.method
: (Optional) Defaults toPOST
for Webhooks if not otherwise specified.headers
: An object of custom HTTP headers to include in the request.
Condition
: Optional condition expression. Supports:- JSONPath conditions like
"$[?(@.WorkItemType==1)]"
. - expr-eval syntax like
"WorkItem.Id > 100"
.
- JSONPath conditions like
Authentication
:- Sub-object containing
OAuth2
settings, if needed. OAuth2
includesClientId
,ClientSecret
,TokenUrl
,GrantType
, and an optionalSupplements
block for adding further headers/body configurations.
- Sub-object containing
3. OAuth Security
3.1. Overview
The Event Notification API supports endpoints that require OAuth2-based authentication. When an endpoint’s configuration specifies OAuth2 parameters, the system automatically:
- Retrieves or generates access tokens through the defined OAuth2 grant type (e.g., client_credentials, authorization_code, etc.).
- Caches these tokens for their valid duration, reducing redundant calls to the OAuth server.
- Injects the token into the outgoing request’s authorization header before sending the payload.
3.2. Configuration
The configuration for OAuth2 is found under each endpoint’s Authentication
object. For example:
{
"Authentication": {
"OAuth2": {
"ClientId": "<CLIENT_ID>",
"ClientSecret": "<CLIENT_SECRET>",
"TokenUrl": "https://<oauth-server>/oauth/token",
"GrantType": "client_credentials",
"Supplements": {
"headers": {
"Content-Type": "application/json"
}
}
}
}
}
Key Fields
ClientId
: OAuth client IDClientSecret
: OAuth client secretTokenUrl
: The URL where the system exchanges credentials for an access tokenGrantType
: Supported values includeclient_credentials
,authorization_code
, and (in some cases)none
for no authenticationSupplements
: An optional JSON object that allows you to provide additional headers or parameters to the token request
3.3. OAuth Grant Types
Currently, two primary OAuth2 flows are implemented:
client_credentials
- Used for machine-to-machine scenarios.
- Requires both
ClientId
andClientSecret
. - Exchanges them at the
TokenUrl
for an access token.
authorization_code
- Used for user-interactive scenarios (not as common for webhooks, but supported if required).
- Requires an
AuthorizationCode
andRedirectUri
to exchange for an access token. - Typically relevant if an external system grants tokens via a user consent page.
Note: A third option,
none
, indicates no authentication is required and thus no token retrieval is performed.
3.4. Token Handling & Caching
Token retrieval is performed via an OAuth2Strategy instance (e.g., ClientCredentialsStrategy
or AuthorizationCodeStrategy
). Each instance is responsible for:
- Constructing the request to the
TokenUrl
. - Parsing the response to extract the
access_token
andexpires_in
fields.
Once retrieved, the token is cached for the duration indicated by expires_in
. If a request for the token occurs again within that window, the system uses the cached token
3.5. Injecting Tokens Into Requests
In the WebhookEndpointProcessor
(or other processors supporting OAuth), an axios
interceptor is applied using axios-token-interceptor
. When an HTTP request is about to be sent:
- The token interceptor checks if an access token is cached.
- If not cached or expired, the interceptor retrieves a new token by calling the appropriate OAuth2 strategy.
- The token is added to the
Authorization
header (Bearer <token>
) automatically.
All subsequent outbound requests reuse the same cached token until it expires, minimizing load on the token server.
3.6. Error Handling & Retries
When the system fails to obtain an OAuth token (e.g., invalid credentials, network error, expired refresh token, etc.):
- It throws an exception that is captured as a
FailureResponse
in theOutboundNotificationHandler
. - If the error is deemed retryable (e.g., network failures), the
isRetriable
flag is set, allowing you to implement retry logic at a higher level. - If the error is a client-side authentication error (4xx indicating invalid credentials), you may need to fix the configuration.
3.7. Extending OAuth Support
For more specialized or custom OAuth flows:
- Create a new class implementing the base
OAuth2Strategy
interface, handling specifics of your flow (e.g., custom header values, multiple token endpoints, refresh tokens, etc.). - Update the processor (e.g.,
WebhookEndpointProcessor
) to instantiate the new strategy ifGrantType
matches. - Register any additional fields in the endpoint’s configuration (e.g., refresh token parameters, additional scopes) as needed.