Event exports
EVA supports bidirectional communication; sometimes, pulling data alone is just not the right fit for your integration. For instance, when you want to trigger a workflow when an order is paid, or send out emails when a shipment is sent. Event exports ("webhooks") provide a valuable toolset for integrations to achieve this.

Because EVA is a real-time, event-based platform, we export these updates in real-time. When you look at the fundamentals, it's just a matter of specifying the endpoint to which the updates should be routed and you're all set.
These event exports themselves are of a simple nature and CANNOT be altered to include more information. If additional data is required after receiving an export, you can use services such as GetOrder
based on the ID as specified in the export.
Event export basics
Let's start you off with the basics of event exports: our main differentiators for event messages and the information the messages can include.
Event contents
The event export contains a bunch of identifiers, with our two main differentiators being Target
and EventType
. These two properties determine the nature of the event export.
The Target
can be seen as the subject of the event export, while the EventType
then defines what has happened for the Target
. To make this clearer with an example: a Target can be Order, with Type being Created or Shipped.
A complete list of the possible targets and their respective event types can be found lower on this page at Possible targets and event types.
Properties
The body of the event contains the following properties:
Property | Type | Description |
---|---|---|
UID | String | Unique identifier for the event. |
CreationTime | String | Time at which the event was created in UTC (in ISO8601 format). |
Timezone | String | Timezone in which the event was created. |
Target | String | Event subject. |
EventType | String | Actual event for the target. |
Identifier | String | The Target's EVA ID. |
BackendID | String | The target's Backend ID. |
BackendSystemID | String | The ID of the system where the BackendID originated from, ensuring a unique combination. |
Data | Object | Additional data for the event. This will always be an object. However, the properties may vary, please see the example events below. |
Best practices
It's possible to create an endpoint and have all events sent to it, unfiltered and unverified - it's easy, but inadvisable. So before you head off and create your endpoint and your configurations, there are several things we strongly advise you to take in mind.
Filter
For one, EVA already sends out thousands of events, per second. Your endpoint might be up to the challenge, but a few improvements to EVA onwards and the amount of events might have doubled. Queues will form and your flows will start getting delayed, in short: don't skip Event specification.
This filtering goes hand in glove with the creation of multiple endpoints: if you have critical business flows based on event triggers, create a second endpoint, or a third, or.. You can see where we're going with this. In the same vein, if you're performing BI based on events, create another dedicated endpoint.
Verify
Ensure you won't take just anyone's own Order paid event at face value with our two lines of defense: Authorization and Verification.
Monitor
Monitor if your events are reaching your endpoint and are being processed properly. You might have your own software already doing so, but you can also set up EVA's Event exports monitor.
Synced or async
If you kick-off certain business flows based on your webhooks, which then need to be completed before your endpoint responds with a 200 (OK) message, then we calls this as being processed in-sync. If your endpoint responds with the confirmation right away upon receipt, we consider it a-sync.
Because your other flows may take several seconds to finish up, we advise you to set it up a-sync. This avoids your webhooks being delayed due to the Exponential backoff.
Event export configurations
Now that we've got you covered on the principles of our event exports, it's time to jump into event export configurations.
Settings
First off, we've got two settings that enable event exports for your environment.
Title | Value | Description |
---|---|---|
EventExports:Enabled | true | This setting enables event exports in general |
EventExports:EnableEventExportConfigurations | true | This additional setting unlocks the use of multiple endpoints |
Most existing environments (pre-core 2.0.678) run solely on EventExports:Enabled
, which limits you to a single endpoint. Although we do recommend you to diversify your endpoints, enabling the EventExports:EnableEventExportConfigurations
setting for existing environments requires additional work by New Black. Please contact your TL to get this up and running for you.
Services
We provide a set of services that can be used for event export configuration. The most basic form of event export configuration is configured as follows:
{
"Name": "BasicEventExportConfiguration",
"Status": "1",
"Endpoint": "https://hookb.in/je0Zbx07Wdt9dlMMmnzZ",
"Config": {
"ResponseMode": 1
}
}
This simple configuration only specifies an endpoint. This will receive all possible event exports, does not require authentication and does not care about the contents of your response message.
Status
Status indicates whether or not the configuration should be used:
ResponseMode
The response mode specifies what response EVA should expect when firing off webhooks towards this configuration:
Authorization
In the authorization object we can define either a StaticBearerToken
(as specified by you, or us) or a StaticUnschemedToken
. When given, EVA will use it as authentication towards the specified endpoint.
{
"Config": {
"Authorization": {
"StaticBearerToken": "",
"StaticUnschemedToken": ""
},
"ResponseMode": 1
}
}
Event specification
In the MessageTypes
object we can specify what messages we want to export to the endpoint. Leaving it empty will export all possible event types. To exclude event types, use ExcludedMessageTypes
instead. Only specifying TargetID
will send all EventTypes for that target.
A complete list of possible Targets and EventTypes can be found a little lower on this page.
As a reminder, using this kind of filtering for your endpoint keeps it running smoothly.
{
"Config": {
"MessageTypes": [
{
"TargetID": 1, // 1 = order
"EventType": "created"
}
]
}
}
When specifying both MessageTypes
and ExcludedMessageTypes
, the result is MessageTypes
minus ExcludedMessageTypes
.
OU specification
In the IncludeOrganizationUnits
object we can specify for what OU we want to export events to the endpoint. Leaving it empty will export the events for all possible OUs. To exclude OUs, use ExcludeOrganizationUnits
instead.
{
"Config": {
"IncludeOrganizationUnits": [1, 2, 3]
}
}
When specifying both IncludeOrganizationUnits
and ExcludeOrganizationUnits
, the result is IncludeOrganizationUnits
minus ExcludeOrganizationUnits
.
Additional services
GetEventExportConfiguration
GetEventExportConfigurations
GetEventExportTargetTypes
DeleteEventExportConfiguration
UpdateEventExportConfiguration
List of possible Targets and Event Types
To see what targets EVA supports, call GetEnumValues
for EventExportTarget
:
As for the event types, the list we would have to create would be immense. So if you want to take a look at the kind of event types available, call GetEventExportTargetTypes
with one of your preferred targets. List
calls are available to some target types as well, such as ListCashDeposits
.
Examples
To give you a sense of the kind of information included in various events, we've got some basic examples lined up.
If there's an IsSystemGenerated property, it indicates whether or not the order is an order generated by EVA to mirror an existing sales or purchase order in POSO or SOPO flows.
{
"UID": "b107a139-c641-479c-a164-d333f3d414e7",
"CreationTime": "2021-08-25T16:03:54.777Z",
"TimeZone": null,
"Target": "Order",
"EventType": "created",
"Identifier": "62",
"BackendID": null,
"BackendSystemID": null,
"Region": "euw",
"Data": {
"IsSystemGenerated": false
},
"Attempt": 1
}
{
"UID": "2eb8696f-2278-464a-9395-c01883d72c73",
"CreationTime": "2020-10-23T15:22:04.79Z",
"TimeZone": "Europe/Amsterdam",
"Target": "Shipment",
"EventType": "shipped",
"Identifier": "120",
"BackendID": "SHIPMENT123",
"BackendSystemID": "WMS"
}
{
"UID": "1fe03b2b-d9f9-429f-a33d-dd7a80b515c7",
"CreationTime": "2020-11-11T22:35:39.363Z",
"TimeZone": "Europe/Amsterdam",
"Target": "PaymentTransaction",
"EventType": "confirmed",
"Identifier": "0",
"BackendID": "34dcbc697110d6f27f41429f4f4",
"BackendSystemID": "PUSH"
}
{
"UID": "79a34b6a-40b9-4868-8a1b-5944b503c503",
"CreationTime": "2020-09-21T14:26:51.757Z",
"TimeZone": "Europe/Berlin",
"Target": "Order",
"EventType": "blob_attached",
"Identifier": "203",
"BackendID": "yourownorderID",
"BackendSystemID": "WEBSHOP",
"Data": {
"BlobID": "9903ED01-A73C-4874-8ABF-D2678E3AE23D"
}
}
Stock event flow deep dive
In most cases, the basic examples here above are enough to show you what an event looks like.
Because we're feeling particularly masochistic however, here's an entire flow - consisting of 19 events - which shows what happens from the creation of a PO to its receipt in the store.
For some additional context, in this situation we started out with two kinds of products, but only received one of the two while completing the receipt/order. This way the flow also displays some cancellation events.
{
"UID":"155c86d3-6f1d-41cc-b9a1-de06a2ea8cb3",
"CreationTime":"2023-03-24T08:23:50.1393393Z",
"TimeZone": null,
"Target":"Order",
"EventType":"created",
"Identifier":1322,
"BackendID":"EventEx2",
"BackendSystemID":null,
"Region":"euw",
"Data":{
"IsSystemGenerated":"false"
},
"Attempt":1
}
{
"UID":"8d4a79d3-7dec-4e8d-82ef-7994c20a84a1",
"CreationTime":"2023-03-24T08:29:41.8346067Z",
"TimeZone": null,
"Target":"Order",
"EventType":"placed",
"Identifier": 1322,
"BackendID":"EventEx2",
"BackendSystemID": null,
"Region":"euw",
"Data":{
"IsSystemGenerated":"false"
},
"Attempt":1
}
{
"UID":"ba2ab3a8-0c2c-489c-b142-aa75fd5be8cd",
"CreationTime":"2023-03-24T08:29:41.9541448Z",
"TimeZone": null,
"Target":"Order",
"EventType":"confirmed",
"Identifier":"1322",
"BackendID":"EventEx2",
"BackendSystemID": null,
"Region":"euw",
"Data":{
"IsSystemGenerated":"false"
},
"Attempt":1
}
{
"UID":"a7c3e795-c53b-495d-a73e-41c00fa89358",
"CreationTime":"2023-03-24T10:14:10.8252757Z",
"TimeZone": null,
"Target":"Shipment",
"EventType":"created",
"Identifier": 186,
"BackendID":"StockEx2Shipment",
"BackendSystemID": null,
"Region":"euw",
"Data": null,
"Attempt":1
}
{
"UID":"e52e1f39-7804-4d87-a8f0-c2cff13d122e",
"CreationTime":"2023-03-24T10:47:45.8480108Z",
"TimeZone":null,
"Target":"Order",
"EventType":"ledger_created",
"Identifier":1326,
"BackendID":"StockEx5",
"BackendSystemID": null,
"Region":"euw",
"Data":{
"LedgerType":"Shipped"
},
"Attempt":1
}
{
"UID":"65f3c537-7176-4b54-aef5-5a6989b144b2",
"CreationTime":"2023-03-24T10:47:45.8818842Z",
"TimeZone":null,
"Target":"Shipment",
"EventType":"shipped",
"Identifier": 187,
"BackendID":"StockEx5Shipment",
"BackendSystemID": null,
"Region":"euw",
"Data": null,
"Attempt":1
}
{
"UID":"2a438fb7-36f5-4d96-8426-0542ab198afa",
"CreationTime":"2023-03-24T10:47:45.8841424Z",
"TimeZone":"Europe/Amsterdam",
"Target":"UserTasks",
"EventType":"created",
"Identifier": 1045,
"BackendID": null,
"BackendSystemID": null,
"Region":"euw",
"Data":{
"OrganizationUnitID":"9",
"UserTaskTypeID":"13",
"UserTaskSubTypeID": null
},
"Attempt":1
}
{
"UID":"b6771f2b-babb-48a0-96d5-6dc311ea3304",
"CreationTime":"2023-03-24T10:47:45.8956054Z",
"TimeZone": null,
"Target":"Order",
"EventType":"shipped",
"Identifier": 1326,
"BackendID":"StockEx5",
"BackendSystemID": null,
"Region":"euw",
"Data":{
"IsSystemGenerated":"false"
},
"Attempt":1
}
{
"UID":"59981de3-0147-4774-b1e4-d8f42df8bea7",
"CreationTime":"2023-03-24T10:47:45.8991726Z",
"TimeZone": null,
"Target":"StockMutation",
"EventType":"created",
"Identifier": 501,
"BackendID": null,
"BackendSystemID": null,
"Region":"euw",
"Data":{
"Product":{
"ID":"74",
"BackendID":013-1-978020137056
},
"OrganizationUnit":{
"ID":"11",
"BackendID":"lelystad_warehouse"
},
"Reason":"SentBySupplier",
"Quantity":"-15",
"SourceStockLabel":"Sellable",
"DestinationStockLabel":"Sellable",
"Type":"Adjust",
"OrderLineID": 1814
},
"Attempt":1
}
{
"UID":"686085dc-09e3-414f-a98f-4389146719d7",
"CreationTime":"2023-03-24T10:47:45.9132345Z",
"TimeZone": null,
"Target":"StockMutation",
"EventType":"created",
"Identifier":"499",
"BackendID": null,
"BackendSystemID": null,
"Region":"euw",
"Data":{
"Product":{
"ID": 14,
"BackendID": 002-1-978020137008
},
"OrganizationUnit":{
"ID":"11",
"BackendID":"lelystad_warehouse"
},
"Reason":"SentBySupplier",
"Quantity":"-20",
"SourceStockLabel":"Sellable",
"DestinationStockLabel":"Sellable",
"Type":"Adjust",
"OrderLineID": 1813
},
"Attempt":1
}
{
"UID":"5df398f6-d13a-4955-bb30-034e236fc0bc",
"CreationTime":"2023-03-24T10:47:45.9434689Z",
"TimeZone":"Europe/Amsterdam",
"Target":"StockMutation",
"EventType":"created",
"Identifier": 502,
"BackendID": null,
"BackendSystemID": null,
"Region":"euw",
"Data":{
"Product":{
"ID": 74,
"BackendID":013-1-978020137056
},
"OrganizationUnit":{
"ID": 9,
"BackendID":"almere_store"
}
"Reason":"InTransit",
"Quantity": 15,
"SourceStockLabel":"Transit",
"DestinationStockLabel":"Transit",
"Type":"Adjust",
"OrderLineID": 1814
},
"Attempt":1
}
{
"UID":"2a6078e6-6bba-4b5e-aa7d-7c9c9cacdaa6",
"CreationTime":"2023-03-24T10:47:45.9626382Z",
"TimeZone":"Europe/Amsterdam",
"Target":"StockMutation",
"EventType":"created",
"Identifier": 500,
"BackendID": null,
"BackendSystemID": null,
"Region":"euw",
"Data":{
"Product":{
"ID":14,
"BackendID": 002-1-978020137008
},
"OrganizationUnit":{
"ID": 9,
"BackendID":"almere_store",
},
"Reason":"InTransit",
"Quantity": 20,
"SourceStockLabel":"Transit",
"DestinationStockLabel":"Transit",
"Type":"Adjust",
"OrderLineID":"1813"
},
"Attempt":1
}
{
"UID":"47022d33-a00b-44e2-9cb0-294468e95597",
"CreationTime":"2023-03-24T11:03:05.9854933Z",
"TimeZone": null,
"Target":"Order",
"EventType":"ledger_created",
"Identifier": 1326,
"BackendID":"StockEx5",
"BackendSystemID": null,
"Region":"euw",
"Data":{
"LedgerType":"SplitLine"
},
"Attempt":1
}
{
"UID":"62a312af-6de6-4eb2-af17-fb83fbf486c7",
"CreationTime":"2023-03-24T11:03:06.1618188Z",
"TimeZone": null,
"Target":"Order",
"EventType":"cancelled",
"Identifier": 1326,
"BackendID":"StockEx5",
"BackendSystemID": null,
"Region":"euw",
"Data":{
"IsSystemGenerated":"false"
},
"Attempt":1
}
{
"UID":"8978a6e2-be3f-4875-8c29-ba4d192c3d50",
"CreationTime":"2023-03-24T11:03:06.1660419Z",
"TimeZone": null,
"Target":"Order",
"EventType":"lines_cancelled",
"Identifier": 1326,
"BackendID":"StockEx5",
"BackendSystemID": null,
"Region":"euw",
"Data":{
"FullyCancelledOrderLineIDs":[
"0":1814
],
"PartiallyCancelledOrderLineIDs": null
},
"Attempt":1
}
{
"UID":"b682003f-790b-4374-a249-11aa2bcca52f",
"CreationTime":"2023-03-24T11:03:06.1818537Z",
"TimeZone":null,
"Target":"Order",
"EventType":"ledger_created",
"Identifier": 1326,
"BackendID":"StockEx5",
"BackendSystemID": null,
"Region":"euw",
"Data":{
"LedgerType":"Cancelled"
},
"Attempt":1
}
{
"UID":"129283e0-80d1-4d16-b040-ea94733bc78b",
"CreationTime":"2023-03-24T11:03:06.1841922Z",
"TimeZone": null,
"Target":"Shipment",
"EventType":"received",
"Identifier": 187,
"BackendID":"StockEx5Shipment",
"BackendSystemID": null,
"Region":"euw",
"Data":null,
"Attempt":1
}
{
"UID":"3d158591-6d8c-4e3a-92d5-d10516f28fc6",
"CreationTime":"2023-03-24T11:03:06.1869818Z",
"TimeZone":"Europe/Amsterdam",
"Target":"StockMutation",
"EventType":"created",
"Identifier": 503,
"BackendID": null,
"BackendSystemID": null,
"Region":"euw",
"Data":{
"Product":{
"ID": 14,
"BackendID":002-1-978020137008
},
"OrganizationUnit":{
"ID": 9,
"BackendID":"almere_store",
"Reason":"Received",
"Quantity": 20,
"SourceStockLabel":"Transit",
"DestinationStockLabel":"Sellable",
"Type":"Move",
"OrderLineID": 1813
}
},
"Attempt":1
}
{
"UID":"c1190611-fc68-4966-918a-cf39d27509ea",
"CreationTime":"2023-03-24T11:03:06.1947741Z",
"TimeZone":"Europe/Amsterdam",
"Target":"StockMutation",
"EventType":"created",
"Identifier": 504,
"BackendID": null,
"BackendSystemID": null,
"Region":"euw",
"Data":{
"Product":{
"ID": 74,
"BackendID":013-1-978020137056
},
"OrganizationUnit":{
"ID": 9,
"BackendID":"almere_store",
"Reason":"ReceiptDeficiency",
"Quantity":"-15",
"SourceStockLabel":"Transit",
"DestinationStockLabel":"Transit",
"Type":"Adjust",
"OrderLineID": 1814
}
},
"Attempt":1
}
Endpoint requirements
- Your endpoint needs to respond with 200 (OK) response, with a string body [ACK]. If this is not the case, the event will retry.
- Make sure
Endpoint
(or any intermediate redirect routes) does not resolve to any RFC1918 (in other words: local) addresses. - Make sure
Endpoint
uses the HTTPS protocol which is as up to date as possible - preferrably HTTP/3 Endpoint
should be accessible from EVA- Webhook should accept a POST request on endpoint
- Implement authentication on the endpoint - use preferably both the aforementioned authorization and verification
Verifying the signature
Each request to Endpoint
will have the following headers:
X-EVA-Signing-1
X-EVA-Signing-2
X-Eva-Signing-Headers
.
The latter will contain a comma separated list of all request headers considered in the signing process. This is helpful when operating behind a reverse proxy that might add some headers. Make sure this proxy does not remove any headers, as this will render you unable to validate the requests.
To validate the requests, take the following three steps.
1. Hashing the headers
Only use the headers as specified in X-Eva-Signing-Headers
Transform the headers from this:
Content-Type: application/json;charset=utf-8
Content-Length: 12345
Authentication: Bearer MySecretToken
to:
Authentication=Bearer MySecretToken
Content-Length=12345
Content-Type=application/json;charset=utf-8
Sort the headers, first by header key, then by header value
Next make sure the header key and value are separated by a=
Also make sure the line breaks are\n
Then we transform this text to bytes (use UTF-8) and hash it using SHA256. Save the hash as a uppercase hexadecimal string.
2. Hashing the body
Hash the body using SHA256. Save the hash as a uppercase hexadecimal string.
3. Calculate the hash
Calculate the final hash as follows (store as uppercase hexadecimal string):
final_hash = sha256(header_hash + body_hash + api_key)
This hash should match either X-EVA-Signing-1
or X-EVA-Signing-2
.
Testing, queues and errors
Testing event exports is extremely easy. Make sure you have an endpoint for testing and make sure your settings are correct. Don't have an endpoint for testing? We advise Hookbin. Now just use one of our frontends (or services) to mutate stock, place an order or anything else, and check your endpoint for the exported events.
Queuing
As mentioned throughout this document, your endpoint needs to respond with a 200 (OK) message. If it doesn't, the event is seen as failed and EVA will retry - at least up to a certain point. The way EVA handles this also differs per type of environment.
Failed
When an export fails on a PRODUCTION environment, EVA will try again based on exponential backoff: the message will be sent again after 5 seconds, 10 seconds, 20 seconds, etc. up to 24 hours. After this, you can no longer reach the message.
On TEST environments however, where are endpoints are often used liberally, EVA automatically disables your endpoint after either 200 failed messages, or 30 minutes of consecutive error messages. In this case the endpoint will get Status Error (3), but you can simply set it to Status Enabled (1) again if necessary.
Endpoint disabled
This failing/retrying is different from having a disabled endpoint. If you manually disable the endpoint on PRODUCTION, a different kind of queue will automatically start. All your messages will be saved up for you until either the size of the queued messages reaches 20gb, or up to 7 days - whichever comes first.
Scripting Event exports
To customize exports based on filters, check the extension point EventExport.