Creating Handlers to Receive Events with IFX
This guide is designed for developers to understand how to create event handlers within an integration to receive both asynchronous (async) and synchronous (sync) events.
Prerequisites
Before you begin writing handlers, you should:
- Have an integration up and running on your local machine
- Have a PAT
- Understand the event types, what they do, and how they are triggered
- Determine which event(s) you would like to listen to
- Subscribe to those events
Tutorial: Asynchronous Handler
Node.js
Creating an Event Handler
Navigate to /src
directory. There are a few files you’ll need to know about.
eventsRouter.json
: handles routing events to their handlers. You can name your handlers whatever you like, but something a bit descriptive for what the handler is… handling.
This defines which handler should process which events. The keys should be the names of the events handler files (without extension) and their value should correspond to the events that they are going to process. In case more than one handler specifies the same event, the first one that is defined will be the one that will process the event.
{ "storyUpdateHandler": ["story:create", "story:update"], "storyDeleteHandler": ["story:delete"]}
When you run npm run localTestingServer
or npm install
, eventsHandlers.js
is auto-generated to include the handler files defined in eventsRouter.json
. There is no need to modify this file, it’s just to show how it works.
const defaultHandler = require('./eventsHandlers/defaultHandler');const storyUpdateHandler = require('./eventsHandlers/storyUpdateHandler');const storyDeleteHandler = require('./eventsHandlers/storyDeleteHandler');
module.exports = { defaultHandler, storyUpdateHandler, storyDeleteHandler,}
Now inside of the /eventsHandler
directory we need to add these files.
const storyUpdateHandler = (event) => { console.log(`my logic in storyUpdateHandler`); console.log(JSON.stringify(event, null, 2));}
module.exports = storyUpdateHandler;
const storyDeleteHandler = (event) => { console.log(`my logic in storyDeleteHandler`); console.log(JSON.stringify(event, null, 2));}
module.exports = storyDeleteHandler;
Now if I run npm run localTestingServer
I see the following with no errors:
> @myorgname/myorgname-bg4@1.0.0 prelocalTestingServer> node node_modules/@arcxp/arcxp-ifx-node-sdk/eventsHandlersModuleGenerator
The 'eventsHandlers' file content:const defaultHandler = require('./eventsHandlers/defaultHandler');const storyDeleteHandler = require('./eventsHandlers/storyDeleteHandler');const storyUpdateHandler = require('./eventsHandlers/storyUpdateHandler');
module.exports = { defaultHandler, storyDeleteHandler, storyUpdateHandler,}
> @myorgname/myorgname-bg4@1.0.0 localTestingServer> node node_modules/@arcxp/arcxp-ifx-node-sdk/localTestingServer
Local Server Started at http://127.0.0.1:8080/ifx/local/invoke
We can send an event to the handler by POSTing to http://localhost:8080/ifx/local/invoke
with a payload:
{ "organizationId": "myorgname", "currentUserId": "", "key": "story:update", "typeId": 1, "body": { "date": "2022-10-05T20:28:10.958Z", "subheadlines": { "basic": "" }, "description": { "basic": "" }, "language": "", "source": { "system": "composer", "name": "myorgname", "source_type": "staff" }, "type": "story", "id": "3CPAWBS43ZEB7GDZC73MT6ORY4", "last_updated_date": "2022-10-05T20:28:05.008Z", "workflow": { "status_code": 1 }, "version": "0.10.7", "canonical_website": "sample", "headlines": { "tablet": "", "print": "", "meta_title": "", "native": "", "web": "", "mobile": "", "basic": "fadfasdf", "table": "" }, "created_date": "2022-10-05T20:28:05.008Z", "_id": "3CPAWBS43ZEB7GDZC73MT6ORY4" }, "version": 2, "uuid": ""}
In my Terminal logs:
my logic in storyUpdateHandler{ "version": 2, "key": "story:update", "body": { "date": "2022-10-05T20:28:10.958Z", "subheadlines": { "basic": "" }, "description": { "basic": "" }, "language": "", "source": { "system": "composer", "name": "myorgname", "source_type": "staff" }, "type": "story", "id": "3CPAWBS43ZEB7GDZC73MT6ORY4", "last_updated_date": "2022-10-05T20:28:05.008Z", "workflow": { "status_code": 1 }, "version": "0.10.7", "canonical_website": "sample", "headlines": { "tablet": "", "print": "", "meta_title": "", "native": "", "web": "", "mobile": "", "basic": "fadfasdf", "table": "" }, "created_date": "2022-10-05T20:28:05.008Z", "_id": "3CPAWBS43ZEB7GDZC73MT6ORY4" }, "typeId": 1, "time": null, "uuid": ""}
Java
In this section will you will create an async event handler in your integration. The example will be shown in Eclipse but any IDE can be used.
For this example assume the following:
Organization | arcifx |
Integration Name | test-client-11 |
Integration Version | 1.0.6 |
IFX SDK Version | 3.1.1 |
For your integration, substitute your own organization and integration name.
Creating an Event Handler
To receive events on your local machine a WebSocket must be used. Your host should point to the sandbox environment.
Open IFX project and create a new Java Class that extends com.arcxp.platform.sdk.handlers.async.EventHandler
Your handler code should be under the com.<organization>
package in your project.
package com.arcifx.events;
import com.arcxp.platform.sdk.handlers.async.EventHandler;import com.arcxp.platform.sdk.handlers.async.EventPayload;
public class MyEventHandler extends EventHandler {
@Override public void handle(EventPayload payload) { // TODO Auto-generated method stub }
}
Add the ArcAsyncEvent
annotation above the class definition using the event that you would like to listen for in the annotation. In the example below commerce:verify_email
is the event the handler is listening for:
package com.arcifx.events;
import com.arcxp.platform.sdk.annotations.ArcAsyncEvent;import com.arcxp.platform.sdk.handlers.async.EventHandler;import com.arcxp.platform.sdk.handlers.async.EventPayload;
@ArcAsyncEvent({"commerce:verify_email"})public class MyEventHandler extends EventHandler {
@Override public void handle(EventPayload payload) { // TODO Auto-generated method stub }
}
Next, determine the behavior of the handler. For starters just log out the event body.
In the example code below the payload key should be the same as the ArcAsyncEvent
annotation commerce:verify_email
. The body will be a com.fasterxml.jackson.databind.node.ObjectNode
that represents the JSON event that was received:
package com.arcifx.events;
import com.arcxp.platform.sdk.annotations.ArcAsyncEvent;import com.arcxp.platform.sdk.handlers.async.EventHandler;import com.arcxp.platform.sdk.handlers.async.EventPayload;
@ArcAsyncEvent({"commerce:verify_email"})public class MyEventHandler extends EventHandler {
@Override public void handle(EventPayload payload) { System.out.println("Received event " + payload.getKey() + " " + payload.getBody()); }
}
Invoking the event
The SDK will add the WebSocket for you based on the configurations in the app-local.properties
file. Ensure app=identity
. Example properties file:
basePackage=com.arcifxdeveloperKey=testhost=api.sandbox.arcifx.arcpublishing.comsiteHost=https://arcifx-arcifx-sandbox.cdn.arcpublishing.comapp=identityorg=arcifxsite=arcifxtracing=false
Build your integration with the command ./mvnw -s .mvn/wrapper/settings.xml clean package spring-boot:repackage install
Run your integration with the command java -jar arcifx-test-client-11-1.0.6.jar
To trigger the workflow that will send the commerce:verify_email
event, create a new account in sandbox:
curl --location --request POST 'https://api...arcpublishing.com/identity/api/v1/signup' \--header 'Content-Type: application/json' \--header 'Authorization: Bearer ' \--data-raw '{ "identity":{ "userName":"testuser01", "credentials":"1234567_Aa" }, "profile":{ "firstName":"John", "lastName":"Doe", "displayName":"John_Doe_01", "gender":"MALE", "email":"testuser01@washingtonpost.com", "picture":"image.jpg", "dob":"10-12-1999", "contacts":[{ "phone":"312-555-5674", "type":"HOME" }], "addresses":[{ "line1":"123 Main St", "locality":"Chicago", "region":"IL", "postal":"60618", "country":"US", "type":"HOME" }], "attributes":[{ "name":"name1", "value":"value1", "type":"String" }, { "name":"name1", "value":"value1", "type":"String" }] }}
If there are errors or integrations to services that are needed, check out the error handling section.
Tutorial: Synchronous Handler
Java
The synchronous event provides clients a way to implement custom logic by using ArcSyncEvent
annotation, and then send back a response to the originating app.
Understanding “Before” and “After” Events
Apps can offer “before” or “after” sync events, which allow you to implement custom logic before and after an API call. The application’s event will be waiting on a response from your integration. To understand the behavior of the event, refer to the application’s documentation, or the IFX Event Menu
In these examples we’ll use the ArcSyncEvent
handler using IntelliJ but any IDE can be used.
All examples are using this setup (when working on your integrations, substitute all myorgname with your own org and site setup):
Organization | myorgname |
Integration Environment | sandbox |
Integration Name | ifx-1 |
IFX SDK Version | 1.3.1 |
developerKey | test |
Since we will use the synchronous events from the Sales app, the app should be set to sales sales in the app-local.properties
(based on the where the events are initiated, you will need to change the app to identity, pim, or sales accordingly.) The file should look like:
basePackage=com.myorgnamedeveloperKey=testhost=api.sandbox.myorgname.arcpublishing.comsiteHost=https://myorgname-myorgname-sandbox.cdn.arcpublishing.comapp=salesorg=myorgnamesite=myorgnametracing=false
Add your PAT to the app-local.properties
Sync Example 1: Modify a SKU number
In the below example, we will create a SampleSyncEventHandler
handler to modify the payload and override the sku.
Steps
Open your integration project with your preferred IDE and add a class named SampleSyncEventHandler
which extends com.arcxp.platform.sdk.handlers.sync.RequestHandler
from the IFX SDK under the com.<organization>
package. Then implement the handle method like below:
Add the @ArcSyncEvent
annotation to the class using the namespace for the event that you would like to listen for in the annotation.
package com.myorgname.events.sync;
import com.arcxp.platform.sdk.annotations.ArcSyncEvent;import com.arcxp.platform.sdk.handlers.sync.RequestHandler;import com.arcxp.platform.sdk.handlers.sync.RequestPayload;
@ArcSyncEvent({"commerce:test_sync_event"})public class SampleSyncEventHandler extends RequestHandler { @Override public void handle(RequestPayload requestPayload) { }}
If sku is not null, we will modify its value and set it as the response payload. Otherwise, we will throw a RequestException
with custom code and message.
package com.myorgname.events.sync;
import com.fasterxml.jackson.databind.ObjectMapper;import com.fasterxml.jackson.databind.node.ObjectNode;import org.springframework.beans.factory.annotation.Autowired;
import com.arcxp.platform.sdk.annotations.ArcSyncEvent;import com.arcxp.platform.sdk.handlers.sync.RequestException;import com.arcxp.platform.sdk.handlers.sync.RequestHandler;import com.arcxp.platform.sdk.handlers.sync.RequestPayload;
@ArcSyncEvent({"commerce:test_sync_event"})public class SampleSyncEventHandler extends RequestHandler { @Autowired private ObjectMapper objectMapper;
@Override public void handle(RequestPayload requestPayload) { ObjectNode request = requestPayload.getBody(); if (request.get("sku").asText() != null ) { // Creating response body ObjectNode response = objectMapper.createObjectNode(); response.put("sku", "test2"); // Set to the body of requestPayload requestPayload.setBody(response); } else { throw new RequestException("E0003:Unknown sku"); } }}
Build your integration with the command ./mvnw -s .mvn/wrapper/settings.xml clean package spring-boot:repackage install
Run your integration with the command java -jar target/myorgname-ifx-1-1.0.0.jar
Sync Example 2: Verification Before Cart Add
In this example, the event happens before the request payload is processed by the originating application, allowing you to inject custom logic before it’s added to the cart.
We will create a handler to perform a simple custom validation to prevent users from adding more than one item to the cart at the same time. If this occurs, we will throw a RequestException
with a custom error code and a message. IFX will delegate this message back to originating application to become a custom error response following the error contract:
{ "httpStatus": 400, "code": "E0001", "message": "Only one item can be added to cart"}
Steps Open your integration project with your preferred IDE and add a class named CartAddBefore
which extends com.arcxp.platform.sdk.handlers.sync.RequestHandler
from the IFX SDK under the com.<organization>
package. Then implement the handle method like below:
Add the @ArcSyncEvent(“commerce:cart_add_before”)
annotation on the class with the commerce:cart_add_before
event name
@ArcSyncEvent("commerce:cart_add_before")public class CartAddBefore extends RequestHandler { @Override public void handle(RequestPayload requestPayload) {
}}
We will validate against the cart items and throw a RequestException if there is more than 1 item:
@ArcSyncEvent("commerce:cart_add_before")public class CartAddBefore extends RequestHandler { @Override public void handle(RequestPayload requestPayload) { ObjectNode request = requestPayload.getBody();
if (request.get("items") != null && request.get("items").size() > 1) { throw new RequestException("E0001:Only one item can be added to cart"); } }}
Build your integration with the command ./mvnw -s .mvn/wrapper/settings.xml clean package spring-boot:repackage install
Run your integration with the command java -jar target/myorgname-ifx-1-1.0.0.jar
Be sure the commerce:cart_add_before
event is enabled for your sandbox environment.
Call the items to cart public API with the developerkey
test in the headers, so Commerce will invoke your integration. For example:
curl --location --request POST 'https://<domain_of_site>/sales/public/v1/cart/item' \--header 'content-type: application/json' \--header 'developerkey: test' \--data-raw '{ "items": [ { "sku": "test1", "eventId": 762450478228893, "quantity": 1 }, { "sku": "test2", "eventId": 762450478228893, "quantity": 1 } ]}'
Replace the payload with the data available in your environment.
Then you should see an error response returned from the call, and Commerce won’t add these items to cart.
Sync Example 3: Logic After Cart Add
In this example, the event happens after the request payload is processed by the originating application, allowing you to inject custom logic after it’s added to the cart.
We will create a commerce:cart_add_after
handler to simply print out the current cart size.
Steps
Open your integration project with your preferred IDE and add a class named CartAddAfter
which extends com.arcxp.platform.sdk.handlers.sync.RequestHandler
from the IFX SDK under the com.<organization>
package. Then implement the handle method like below:
Add the @ArcSyncEvent(“commerce:cart_add_after”)
annotation:
@ArcSyncEvent("commerce:cart_add_after")public class CartAddAfter extends RequestHandler { @Override public void handle(RequestPayload requestPayload) {
}}
The body of the requestPayload
is the response payload sent from Commerce. We will log the size of the items in the cart:
@ArcSyncEvent("commerce:cart_add_after")public class CartAddAfter extends RequestHandler {
private static final Logger LOG = LoggerFactory.getLogger(CartAddAfter.class);
@Override public void handle(RequestPayload requestPayload) { LOG.info("Cart size is {}", requestPayload.getBody().get("items").size()); }}
Build your integration with the command ./mvnw -s .mvn/wrapper/settings.xml clean package spring-boot:repackage install
Run your integration with the command java -jar target/myorgname-ifx-1-1.0.0.jar
Be sure the commerce:cart_add_after
event is enabled for your sandbox environment.
Call the items to cart public API with the developerkey
test in the headers, so Commerce will invoke your integration. For example:
curl --location --request POST 'https:///sales/public/v1/cart/item' \--header 'content-type: application/json' \--header 'developerkey: test' \--data-raw '{ "items": [ { "sku": "test1", "eventId": 762450478228893, "quantity": 1 } ]}
Help us improve our documentation! Let us know if you do not find what you are looking for by posting suggestions to Our Ideas Portal.