Webhook Message Reference
Introduction
This is a companion document to the Notification API specification. It details the format of messages which will be delivered to your webhook.
The following information is covered in the following sections:
- Base Message: The standard elements that will be present in all received messages.
- Response Format: The standard response format you must use for replying to all messages.
- Authentication: How to secure your webhook endpoint by validating EML as the sender.
- Ping Message: A message used for testing connectivity.
- Transaction Message: A message which notifies you of a transaction which has occurred.
- Undeliverable Alert Messages: A message which notifies you that a previous transaction message was not delivered to you successfully.
- Batch Transfer Message: A message which notifies you of status for Batch transfer you are running.
- Text Message:
BETA
A simple text message used for simple notifications. - Run Template Message:
BETA
A message which notifies you of status for template you are running. - Save Template Message:
BETA
A message which notifies you of status for template you are adding to the system.
Base Message
The basic format is the same for all messages sent to the webhook.
{
/* UUID for this message */
"id": "7fe9e91d-ea32-4102-b646-edd06081868b",
/* The UUID of the webhook which the message is being delivered to */
"hook_id": "83efe158-df15-4e03-a8e6-a0fbd09d7115",
/*
* The canonical URI for managing this hook via the Notification API.
* Check this URI to see whether you are receiving messages from production or test.
*/
"hook_management_uri": "https://ws.emerchants.com.au/3.0/hooks/83efe158-df15-4e03-a8e6-a0fbd09d7115",
/* Timestamp for when we sent the message, in ISO-8601 format */
"timestamp": "2018-04-19T10:29:03.23684Z",
/* Message type. One of "transaction", "ping" or "undeliverable_alert" */
"type": "transaction",
/* Payload format version used by this message. Follows SemVer. */
"version": "1.0.0",
"data": {
/* Varies based on message type */
}
}
HTTP Elements
Each message sent to the webhook will conform to the following:
- An HTTP 1.1 request, using the POST method/verb.
- The request body is text encoded in UTF-8, without a leading byte-order mark.
- The text is JSON formatted, as per RFC7159 and ECMA-404.
- The Content-Type header is:
application/json
. - An Authorization header is set. See the section on Authentication.
- An X-Message-Specification header is set, as detailed below.
X-Message-Specification
request header
The For your flexibility, in addition to providing the type
and version
properties on the base message, we will also set the X-Message-Specification
header on each request. This header contains the identical type and version information, arranged in the format of: type@version
.
An example is:
X-Message-Specification: [email protected]
For some software environments, it may be preferable to use this header rather than the properties on the base message, as it allows you to predict the complete schema of the request body before reading or parsing a single byte of it.
Versioning and Forwards Compatibility
The message specifications defined in this reference are versioned according to SemVer. You should implement your webhook endpoint to be forward-compatible with new minor versions.
For example, you may implement a webhook endpoint to conform with [email protected]
, and we may, in the future, begin sending messages to that endpoint which conform with: [email protected]
.
In order to handle this situation gracefully, your implementation will need to ignore any JSON properties which have an unrecognised name. This way, any new properties which we add during a new minor version will be ignored by your implementation, and you can continue to treat the message as if it conformed to the previous version.
Also note that we do not consider the order which JSON properties appear to be part of the version specification. To be forwards-compatible, your implementation should not expect properties to appear in any particular order.
If we need to make a breaking change to a message specification, we will do this with a new major version. We will endeavour to support each major version on a long term basis, and allow you to choose the major version which will be used for messages delivered to each of your webhook endpoints.
Avoid Environment Confusion
The URI for your webhook is self-managed via the Notification API. Changes to this URI are applied immediately, and EML will not review changes to ensure the URI is correct. Because of this, you need to take care to avoid confusion between environments. Consider that it may be potentially damaging for you to process a test transaction in the production environment, or a production transaction in a test environment.
To provide you with an additional safety-net, the base message contains the hook_management_uri
property. You can use this property to determine the environment where the message was generated, and filter out messages which are erroneously delivered to your webhook endpoint. We strongly recommend that you should reject any message where the hook_management_uri
does not contain the hostname you are expecting.
Responding to messages
We will wait for you to send an HTTP response to all messages sent to your webhook URI. Providing us with a valid response allows us to detect that the message was received and processed by you correctly.
If you have enabled Reliable Delivery for your webhook, we will store any transaction message where you fail to provide a valid response.
Response format
The response format for all messages types is the same. Your response MUST have:
- An HTTP 200 (OK) Status Code
- A content type of
application/json
- A JSON body which echoes the id property of the request.
Any response which does not match all of these criteria will be considered a failed delivery.
Example response body
{
"id": "7fe9e91d-ea32-4102-b646-edd06081868b"
}
Time limit
In addition to matching the response format above, you must also respond to us within the time limit. Our time limit is currently configured to 10 seconds, though you should try to respond as quickly as possible.
Our suggested benchmark is to respond in 3 seconds or less under normal load conditions.
Authentication
HMAC refers to hash-based message authentication code, a widely used scheme for message authentication through cryptography.
EML generates and attaches an HMAC to each message sent to your webhook endpoint. By verifying the HMAC, you can authenticate that an incoming message has originated from EML, giving you assurance that a message has not been sent to your endpoint by a malicious actor.
Keys
Each HMAC is calculated through the use of a shared 256bit key. This is a key chosen by you when registering or updating your webhook.
A key is comprised of two components:
hmac_key_id
, a unique identifier for the keyhmac_key_secret
, the 256bit key itself.
For each message sent to your webhook, in addition to the hmac_value
, we will also include the hmac_key_id
of the key used to sign the message. Use it to look up the correct hmac_key_secret
needed to validate the HMAC. This is useful for messages delivered in the moments immediately after updating your key, which may still be signed with your previous key.
If you change the hmac_key_secret
for your webhook, be sure to also change hmac_key_id
.
Authorization Header
Each HTTP request sent to your webhook will have an Authorization header set, containing your hmac_key_id
and the hmac_value
, separated by a semicolon:
Authorization: HMAC_SHA256 your_key_id;hmac_value
The hmac_value
is a 256bit hex-encoded HMAC. You can verify it by re-calculating the value for yourself, and then comparing it to the value in the header.
Your calculation should take the form of: HMAC_SHA256(hmac_key_secret, data)
, where:
- hmac_key_secret refers to the key as described above. This is a bitstring of exactly 256 bits/32 bytes.
- data refers to the complete body of the received HTTP request. This is a bitstring which must match the original JSON payload and UTF-8 encoding of the request.
Example
Authorization: HMAC_SHA256 b16842a9-5e7b-4f54-a3ce-112ec93dc0a9;efbe68956f95dc3a16ed839bc0dfa463786b6ce4295c1f4670f57fed1f6b8992
Sample - .NET Framework
More information about HMACSHA256 here.
using System.Globalization;
using System.Security.Cryptography;
class Program
{
void Main()
{
var messageBodyJsonString = @"<message body goes here>";
var secretHexString = "<secret key as a HEX string goes here>";
UTF8Encoding textEncoding = new UTF8Encoding();
//Get bytes from the message body using UTF8 encoding.
var messageBodyBytes = textEncoding.GetBytes(messageBodyJsonString);
//Convert secret key HEX string to bytes.
var secretKeyBytes = ConvertHexStringToByteArray(secretHexString);
//Create HMACSHA356 object from secret key.
using (var hmac = new HMACSHA256(secretKeyBytes))
{
//Compute the HMAC SHA256 hash using the message body bytes.
var digest = hmac.ComputeHash(messageBodyBytes);
var hex = new StringBuilder(digest.Length * 2);
foreach (var b in digest)
{
hex.AppendFormat("{0:X2}", b);
}
//Here's the HMAC value that needs to be validated with the Auth header HMAC value.
var hmacValue = hex.ToString();
Console.WriteLine(hmacValue);
}
}
byte[] ConvertHexStringToByteArray(string hexString)
{
if (hexString.Length % 2 != 0)
{
throw new ArgumentException(String.Format(CultureInfo.InvariantCulture, "The hex string cannot have an odd number of digits: {0}", hexString));
}
byte[] data = new byte[hexString.Length / 2];
for (int index = 0; index < data.Length; index++)
{
string byteValue = hexString.Substring(index * 2, 2);
data[index] = byte.Parse(byteValue, NumberStyles.HexNumber, CultureInfo.InvariantCulture);
}
return data;
}
}
Sample - Java
More information on Mac here.
import java.util.Random;
import java.util.ArrayList;
import java.text.SimpleDateFormat;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.security.NoSuchAlgorithmException;
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import javax.xml.bind.DatatypeConverter;
import java.util.Arrays;
public class Main {
public static void main(String[] args)
{
String secretKey = "<secret key as a HEX string goes here>";
String message = "<message body goes here>";
try{
//Get bytes from the message body using UTF8 encoding.
byte[] messageBytes = message.getBytes("UTF-8");
//Convert secret key HEX string to bytes.
byte[] secretKeyBytes = DatatypeConverter.parseHexBinary(secretKey);
SecretKeySpec secretKeySpec = new SecretKeySpec(secretKeyBytes, "HmacSHA256");
//Setup Mac with HMAC SHA256 algorithm.
Mac hmac = Mac.getInstance("HmacSHA256");
hmac.init(secretKeySpec);
//Here's the HMAC value that needs to be validated with the Auth header HMAC value.
String generatedHash = new String(DatatypeConverter.printHexBinary(hmac.doFinal(messageBytes)));
System.out.println(generatedHash);
}
catch(InvalidKeyException e){
System.out.println("InvalidKeyException thrown.");
e.printStackTrace();
}
catch(UnsupportedEncodingException e){
System.out.println("UnsupportedEncodingException thrown.");
e.printStackTrace();
}
catch(NoSuchAlgorithmException e){
System.out.println("NoSuchAlgorithmException thrown.");
e.printStackTrace();
}
}
}
Sample - Ruby
More information on HMAC here.
require 'openssl'
def hex_to_bin(s)
s.scan(/../).map { |x| x.hex.chr }.join
end
messageBodyJsonString = "<message body goes here>"
secretHexString = "<secret key as a HEX string goes here>"
#Get bytes from the message body using UTF8 encoding.
messageBodyUtf8 = messageBodyJsonString.encode("UTF-8")
#Convert secret key HEX string to bytes.
bstringSecret = hex_to_bin(secretHexString)
#Here's the HMAC value that needs to be validated with the Auth header HMAC value.
mac = OpenSSL::HMAC.hexdigest("SHA256", bstringSecret, messageBodyUtf8)
Sample - Node.js
See a fully worked example for ExpressJS
Sample - iOS/OSX
Use CommonCrypto/CommonHMAC
Regarding Replay Attacks
Be aware that this HMAC scheme is not implicitly resistant to replay attacks. If intercepted, it is possible for an attacker to replay a full message, including our original HMAC. If the replayed message is entirely unmodified, the HMAC will still be valid.
If this is a concern for your particular use case, you can mitigate this risk completely by:
- Checking if you have already processed a message with the same
id
. - Checking that the
timestamp
on the incoming message does not differ significantly from the current time.
Message Type: Ping
Hi there! We're just checking that your webhook endpoint is accessible. You may receive these messages from time-to-time as we check that everything is healthy. You will need to respond to this message successfully in order to initially register your webhook.
Example Ping Message
{
"id": "356c8146-2533-46df-956f-a31a7fa5097a",
"hook_id": "83efe158-df15-4e03-a8e6-a0fbd09d7115",
"hook_management_uri": "https://ws.emerchants.com.au/3.0/hooks/83efe158-df15-4e03-a8e6-a0fbd09d7115",
"timestamp": "2018-04-23T00:03:18.8667276Z",
"type": "ping",
"version": "1.0.0",
"data": { } /* No data for a ping message */
}
Message Type: Transaction
A transaction has occurred on an account which is relevant to you. The filter configuration for your webhook has not filtered this transaction, so accordingly, we are now sending it to you.
The format of the transaction message is demonstrated below. To help you interpret this message, you can read: Transaction Model and Lifecycle.
Example Transaction Message
{
/* As per the base message */
"id": "1635e297-82de-4289-8e11-75a8354f77c0",
"hook_id": "83efe158-df15-4e03-a8e6-a0fbd09d7115",
"hook_management_uri": "https://ws.emerchants.com.au/3.0/hooks/83efe158-df15-4e03-a8e6-a0fbd09d7115",
"timestamp": "2018-04-24T02:43:56.4457276Z",
"type": "transaction",
"version": "1.0.0",
"data": {
/* The id of the logical transaction which is new or updated in this message. */
"logical_transaction_id": 1275061825,
/* The External Account Id of the account on which this transaction has occurred. */
"account_id": "NFNG00FMB",
/*
* Additional information to identify the account holder.
* Note: These values are not relevant to all programs.
*/
"customer_info": {
/* (Optional) The client which this customer belongs to. */
"client_id": 108,
/* (Optional) The client's identifier for this account. */
"client_account_key": "fb4da30e-5202-4574-9955-34df9a8ed577"
},
/* The Company Id which the account belongs to. */
"company_id": "6961189",
/* Where is this logical transaction in its transaction lifecycle? */
"state_change": {
/*
* Which workflow are we following?
* One of: "payment_network", "simple"
*/
"lifecycle": "payment_network",
/* Name of the old and new states */
"old_state": "requested",
"new_state": "authorized",
/* Is this the first message containing this logical_transaction_id? */
"is_new": true,
/*
* Does this state change indicate an adjustment to the previous value
* of the transaction?
*/
"is_adjustment": false
},
/* Details of the current standing of the transaction after this state change */
"details": {
/* The date/time of the original transaction */
"occurred_at": "2018-04-24T02:43:56.4457276Z",
/*
* Where did the transaction originate.
* One of: eml, visa, mastercard, eftpos, blackhawk, epay, incomm
*/
"source": "mastercard",
/*
* Indicates if this is a transaction from a mobile wallet, and
* if so, the name of the wallet provider.
*
* For a mobile wallet transaction, the value will be one of:
* apple, google, or samsung
*
* If the transaction is not a mobile wallet transaction,
* the value will be null.
*/
"mobile_wallet": null,
/* Last four digits of a token used for a tokenised payment */
"mobile_token": "####3004",
/*
* The base amount of the transaction, as charged by the merchant
* and displayed on the receipt.
*
* Includes any cash_amount for a ATM or Cash-out-at-POS transaction.
* Does not include internal or external fee amounts.
*/
"base_amount": {
/*
* The amount, in the minor currency unit.
* i.e., in cents/pence/etc. (-3750 = -$37.50)
*/
"value_minor": -3750,
/*
* An ISO 4217 numeric currency code.
* e.g., 036 = Australian Dollar, 840 = US Dollar.
*/
"currency": "036"
},
/*
* The cash component of an ATM Withdrawal or a Cash-out-at-POS transaction.
*
* Always less than or equal to the base_amount.
*/
"cash_amount": {
"value_minor": 0,
"currency": "036"
},
/*
* Fee amount charged by EML to the account holder on this transaction.
*
* If there are fee(s) on this transaction which attract GST,
* the amount shown here is inclusive of that tax.
*/
"internal_fee_amount": {
"value_minor": 0,
"currency": "036"
},
/*
* External fees charged to the account holder on this transaction.
* e.g., ATM owner fees
*/
"external_fee_amount": {
"value_minor": 0,
"currency": "036"
},
/*
* (Optional) Details about the merchant/acquirer currency.
* This sub-element will be null for domestic transactions.
*/
"foreign_exchange": {
/*
* The source/merchant currency.
* An ISO 4217 numeric currency code.
*/
"source_currency": "840",
/*
* The conversion rate between the source currency and the account
* holder's currency.
*
* base_amount = merchant_price * conversion_rate
*/
"conversion_rate": 1.35
},
/* Merchant/transaction reference. */
"description": "LITTLE RAW DELI PTY LT CARLISLE AU",
/*
* (Optional) Merchant particulars.
* This sub-element may be null for some transactions.
*/
"merchant": {
/* (Optional) Category Code */
"merchant_category": "5812",
/* (Optional) Acquiring institution identification code */
"acquirer_id": "456445",
/* (Optional) Card Acceptor Id (a.k.a. Merchant Id) */
"card_acceptor_id": "5355655",
/* (Optional) Terminal Id */
"terminal_id": "8186258",
/* (Optional) Network supplied merchant name and location */
"card_acceptor_name_location": "LITTLE RAW DELI PTY LT CARLISLE AU"
},
/*
* (Optional) Alternate transaction identifiers.
* This sub-element may be null for some transactions.
*/
"identifiers": {
/* (Optional) Authorisation Id */
"auth_id": "394547",
/* (Optional) System Trace Audit Number */
"stan": "729986",
/* (Optional) Retrieval Reference Number */
"rrn": "734201072853",
/*
* (Optional) Network specific transaction clearing identifier.
* e.g., Visa Transaction Id
*/
"clearing_id": "468108810936341"
},
/* Is the transaction is currently reversed, cancelled or declined? */
"is_void": false,
/*
* Does this transaction indicate a changed account status?
* e.g. An activation/deactivation/etc.
*/
"is_account_status_change": false,
/*
* (Optional) The reason why the transaction was declined.
*
* One of: unknown = 0
* insufficient_funds = 1
* incorrect_pin = 2
* merchant_disallowed = 3
* velocity_exceeded = 4
* account_inactive = 5
* account_expired = 6
* system_error = 7
* other = 8
*
* If it is not a declined transaction, the value is null.
*/
"decline_reason": null
},
/* More information about this specific event. */
"event": {
/* The sequence number of the corresponding transaction history item. */
"id": 1275061825,
/* The EML transaction type code. */
"type_code": "1118",
/* The date and time of the event. */
"timestamp": "2018-04-24T02:43:56.4457276Z",
/*
* (Optional) The Id of the delegated transaction decision.
*
* If this event represents a transaction decision which was delegated
* to a third party, this is the id of that request.
*/
"delegation_request_id": "cd2a7545-df34-4902-9e8b-8818cb92acb2",
/* The net change in balance on the account due to this event. */
"balance_delta" : {
"value_minor": -3750,
"currency": "036"
},
/* The balance on the account after the event. */
"running_balance": {
"value_minor": 0,
"currency": "036"
}
/*
* (Optional) The Settlement Data is the information about settlement date and cycle
*
* This section may be present on clearing transaction types ("1115", "1116", "1125"),
* If EML receive these data from the scheme (Mastercard)
*/
"settlement_data" : {
/* Presented in ISO8601 format. */
"reconciliation_date": "2021-07-15T00:00:00.0000000Z",
"reconciliation_cycle": 1,
/* Presented in ISO8601 format. */
"settlement_date": "2021-07-15T00:00:00.0000000Z",
"settlement_cycle": 1
}
}
}
}
Message Type: Undeliverable Alert
You have enabled reliable delivery in your webhook configuration. This is a notification that a previous delivery to your webhook has failed, and there are one or more undeliverable messages waiting for you to collect. See Reliable Delivery for more details.
We will send these alerts from time-to-time, and you may receive an alert for the same failed delivery more than once. To suppress repeated alerts, you need to dismiss all undeliverable messages using the Dismiss Method.
Example Undeliverable Alert Message
{
"id": "786514a5-3669-4f42-953e-9ac501c68e3b",
"hook_id": "83efe158-df15-4e03-a8e6-a0fbd09d7115",
"hook_management_uri": "https://ws.emerchants.com.au/3.0/hooks/83efe158-df15-4e03-a8e6-a0fbd09d7115",
"timestamp": "2018-04-26T10:24:23.7507276Z",
"type": "undeliverable_alert",
"version": "1.0.0",
"data": {
/* The id of the most recent undeliverable message */
"last_undeliverable": "b1ca21d2-041c-4196-9276-7549666c7571",
/* The timestamp of the most recent undeliverable message */
"last_undeliverable_timestamp": "2018-04-26T10:27:38.9222315Z"
}
}
Message Type: Batch Transfer
You receive these messages when a batch Transfer is created via Account API, when a batch transfer is starting to be processed and when the batch transfer processing is completed.
The format of the Batch Transfer message is demonstrated as below. You don't need to reply to this message
Example Batch Transfer Message
{
"id": "786514a5-3669-4f42-953e-9ac501c68e3b",
"hook_id": "83efe158-df15-4e03-a8e6-a0fbd09d7115",
"hook_management_uri": "https://ws.emerchants.com.au/3.0/hooks/83efe158-df15-4e03-a8e6-a0fbd09d7115",
"timestamp": "2018-04-26T10:24:23.7507276Z",
"type": "batch_transfer",
"version": "1.0.0",
"data": {
/* The id of the batch*/
"id": "4f42-953e-9ac501c68e3b"
/* The Request Id batch transfer provided by you*/
"request_id": "654654654"
/*
* The status of template.
*
* One of: pending = 0
* processing = 1
* succeed = 2
* errored = 3
* canceled = 4
* succeedpartialy = 5
*/
"status": null
/* The text message if any status needs to fill extra information*/
"text_message": "This is a sample text message"
/* The date and time the batchtransfer event occurred. */
"occurred_at": "2022-05-25T02:43:56.4457276Z",
/* (Optional) Information about failed card transfers in the batch*/
"failed_rows" : {
{
"row_id": 1,
"failed_reason": "Source card EAID: AS38R1MLV or AccountId: 59381812 not found."
},
{
"row_id": 3,
"failed_reason": "Debit amount exceeds the maximum transfer limit of 2500."
}
}
}
}
BETA
Message Type: Text You may receive these messages from time-to-time based on message we want to deliver to you.
The format of the text message is demonstrated as below. You don't need to reply to this message
Example Text Message
{
"id": "786514a5-3669-4f42-953e-9ac501c68e3b",
"hook_id": "83efe158-df15-4e03-a8e6-a0fbd09d7115",
"hook_management_uri": "https://ws.emerchants.com.au/3.0/hooks/83efe158-df15-4e03-a8e6-a0fbd09d7115",
"timestamp": "2018-04-26T10:24:23.7507276Z",
"type": "text",
"version": "1.0.0",
"data": {
/* The text message */
"text_message": "This is a sample text message"
}
}
BETA
Message Type: Run Template You receive these messages when you run a extract template via Toolbox API.
The format of the Run Template message is demonstrated as below. You don't need to reply to this message
Example Run Template Message
{
"id": "786514a5-3669-4f42-953e-9ac501c68e3b",
"hook_id": "83efe158-df15-4e03-a8e6-a0fbd09d7115",
"hook_management_uri": "https://ws.emerchants.com.au/3.0/hooks/83efe158-df15-4e03-a8e6-a0fbd09d7115",
"timestamp": "2018-04-26T10:24:23.7507276Z",
"type": "run_template",
"version": "1.0.0",
"data": {
/* The id of the template*/
"id": "1275061825"
/* The name of the template provided by you*/
"template_name": "Transaction report"
/*
* The status of template run.
*
* One of: errored = 0
* waiting = 1
* progressing = 2
* successed = 3
*/
"status": null
/* (Optional) The text message if any status needs to fill extra information*/
"text_message": "This is a sample text message"
}
}
BETA
Message Type: Save Template You receive these messages when you add a template via SAM website.
The format of the Save Template message is demonstrated as below. You don't need to reply to this message
Example Save Template Message
{
"id": "786514a5-3669-4f42-953e-9ac501c68e3b",
"hook_id": "83efe158-df15-4e03-a8e6-a0fbd09d7115",
"hook_management_uri": "https://ws.emerchants.com.au/3.0/hooks/83efe158-df15-4e03-a8e6-a0fbd09d7115",
"timestamp": "2018-04-26T10:24:23.7507276Z",
"type": "save_template",
"version": "1.0.0",
"data": {
/* The id of the template*/
"id": "1275061825"
/* The name of the template provided by you*/
"template_name": "Transaction report"
/*
* The status of template.
*
* One of: errored = 0
* reviewing = 1
* rejected = 2
* saved = 3
*/
"status": null
/* (Optional) The text message if any status needs to fill extra information*/
"text_message": "This is a sample text message"
}
}