# Hook v1
Use hook
to be notified about events in the Procurement Service Bus.
The Procurement Service Bus can send webhooks and email notifications to your application about events that happened in your Party. This mechanism is very useful for receiving invoices. Once an invoice has arrived you will be notified via the registered hooks.
There are several types of hooks. The type of hook depends on the action defined. The two general actions are 'mail' and 'https'.
Hooks can be triggered at several moments. These moments are defined as topics.
# Environment hooks
Receive hook notifications for all the parties within your environment. This is very useful when all party hooks have the same configuration. Using the environment hook will make the partyId hook unnecessary in most cases, and thus reduce the calls that have to be made to enroll a party in the PSB. By default environment hooks run with one queue. This means that the PSB can only send one request at a time. If you need higher throughput, please let us know and we will scale your hook to use multiple parallel queues instead.
DO use the environment hook when certain events for all parties needs to be delivered to one endpoint url.
DO NOT use the environment hook if the endpoint url is different for each partyId.
# Topics
The topic indicates when the hook is triggered.
Topic | Function |
---|---|
InvoiceReceived | Triggered when a document is successfully received by the Procurement Service Bus. |
InvoiceReceivedError | Triggered on a document that is rejected by the Procurement Service Bus because of an error. |
InvoiceSent | Triggered when a document is successfully sent by the Procurement Service Bus and received by another party. |
InvoiceSentError | Triggered on a document that is rejected by the receiver because of an error. This is a permanent error and the document will not be send. |
InvoiceSentRetry | Triggered on a document that is rejected by the receiver because of an error. This can be a temporary failure. Retries will be done. |
SpecialHooks | See if hooks are working correctly. Only the email action can be used. |
HookSent | Triggers when a hook is triggered and a hook message is sent. This means the receiving system is informed about a topic. |
HookSentRetry | Triggers when a hook is triggered but the message was not send because the receiver can’t be reached and a retry will be done. This is an indication that your hook receiver is not working correctly and the Procurement Service Bus is unable to inform you about the status of processed documents. |
HookSentError | Triggers when a hook has stopped retrying. This is a permanent error. |
# Filters
Filters can be used to add certain conditions to trigger hooks. For example, only trigger a ReviewReceived hook when the verdict is "reject"
or only trigger an "InvoiceReceived"
hook when the sender is a given party.
# Adding a filter
Filters can be added via the POST /api/v1/{partyId}/hook
endpoint, using the "filter"
field.
{
"id": "1",
"action": "mailto:techsupport@econnect.eu",
"name": "filter hook ",
"topics": [
"ReviewReceived"
],
"filter": "verdict == \"reject\"",
"isActive": true
}
This hook will only trigger when the verdict for the received review is "reject"
.
Filters use a lambda syntax and allow multiple conditions in one filter, for example:
"sender == \"0106:123\" || sender == \"0106:456\" && verdict.StartsWith(\"acc\")"
# Mail hooks
A mail hook has the following action: "action":"mailto:receiver@example.com"
.
# Include attachment
Mail hooks can also include the document as an attachment, for monitoring or debug purposes for example. To do this, add includeAttachment=true
as a query parameter to the action. Optionally, query parameter targetDocumentTypeId={target}
can be used to specify a target documentTypeId. The attached document will be of this documentTypeId, given a transformation from the original to the target documentTypeId is available. Special characters in the documentTypeId have to be escaped.
"action":"mailto:receiver@example.com?includeAttachment=true&targetDocumentTypeId=urn%3Aoasis%3Anames%3Aspecification%3Aubl%3Aschema%3Axsd%3AInvoice-2%3A%3AInvoice%23%23urn%3Acen.eu%3Aen16931%3A2017%23compliant%23urn%3Afdc%3Anen.nl%3Anlcius%3Av1.0%3A%3A2.1"
Extra options for mailhooks
queryParam | values | description |
---|---|---|
replyTo | reply@to.nl | The email address which will be put in the replyTo parameter so receivers of the email wil reply to this email address instead of the from address. |
bcc | multiple@mails.com, emailaddress@mail.com | The bcc parameter can contain multiple email addresses, every email address will be put in the bcc in the sending email. |
filename | {{id}}{{extension}} | When includeAttachment=true is added as query parameter we will add the attachments in the email, in the mail the attachmens will have the name invoicenumber + extension, For example 123.pdf and 123.xml. We only support these 2 options right now, {{id}}{{extension}} needs to be URL encoded. |
version | 0.9 or 1.0 | Version 0.9 is our default, sender will be noreply@everbinding.nl and our old eVerbinding email template. Version 1.0 has sender based on environment, noreply@accp.econnect.email or noreply@econnect.email and our new eConnect email template. |
templateId | d-1234hjg | The templateId parameter can be used when custom email templates are wanted and configured by eConnect. This will overwrite the default templateId used in the version. |
from | mail@emailaddress.com | This parameter can be used to overwrite the from address which will be sending the invoice. This can only be used with an ApiKey. This will overwrite the default from address used in the version. |
When you want to use your own SendGrid for sending emails, you can add this to the Hook configuration.
"init": {
"apiKey": "SG.key"
}
Mailhook example with all options:
{
"id": "1",
"name": "mail hook",
"action": "mailto:techsupport@econnect.eu?replyTo=support@econnect.eu&from=noreply@econnect.email&bcc=recipient1@mail.com,recipient2@mail.com&version=1.0&includeAttachment=true&filename=%7B%7Bid%7D%7D%7B%7Bextension%7D%7D&templateId=d-6235df557bf54d4b9178dea6db01f74f&targetDocumentTypeId=urn%3Aoasis%3Anames%3Aspecification%3Aubl%3Aschema%3Axsd%3AInvoice-2%3A%3AInvoice%23%23urn%3Acen.eu%3Aen16931%3A2017%23compliant%23urn%3Afdc%3Anen.nl%3Anlcius%3Av1.0%3A%3A2.1%0A",
"init": {
"apiKey": "SG.key"
},
"topics": [
"*Sent"
],
"isActive": true,
"createdOn": "2023-08-24T19:43:28.4804048+02:00"
}
# WebHooks
Webhooks deliver information by pushing a https message to a receiver. This can only be done with https.
# Configure your webhook
You can register the webhook via PUT method on the /Hook
endpoint. The Procurement Service Bus starts sending HTTP POST webhooks once the webhook is registered. Events will only be send when a hook is registered with the corresponding topic.
Define an action with your endpoint URL and append this URL with a # and a secret key. This key will be used to sign requests. Optionally, you can define basic authentication in the action to protect your endpoint in the form of: https://user:pass@your.endpoint#someRandomKey
.
{
"id": "1",
"name": "webhook",
"action": "https://user:pass@webhook.site/a81a0be5-8aad-4110-a40c-aa41a2c6e9ac#secretKey",
"topics": [
"InvoiceReceived"
],
"isActive": true
}
# Testing webhook
For testing the call GET /api/v1/{partyId}/hook/ping
can be used. This call will trigger the InvoiceReceived hook by sending a dummy document to {partyId}
.
# Securing webhook
We use an HMAC SHA256 signature of the payload and include that signature in the request header 'X-EConnect-Signature'. The header value is in the form of 'sha256={signature}', where {signature} is a 64-byte, hexadecimal representation of a SHA256 signature. The signature is computed using a secret key provided by you. Validate this signature to make sure the data is not manipulated and sent from the PSB.
The payload contains a 'sentOn' field. Make sure that this date is not older than 5 minutes to prevent relay attacks.
You could also whitelist our IP addresses.
# Validate Webhook signatures
- Get the signature from the X-EConnect-Signature header.
- Get the json payload from the request body and your secret key.
- Compute the signature using the payload and your secret key. Then stringify the byte[] to a hexadecimal representation and prepend the string with
"sha256="
. - Compare the signatures. (We advise to use a secure compare.)
You could use https://www.freeformatter.com/hmac-generator.html (opens new window) to manually validate the signature. Enter your secret key and select SHA256 as the digest algorithm, then compute the hash.
Compute signature example:
public static string ComputeSignature(string message, string secret)
{
var encoding = new UTF8Encoding();
byte[] keyByte = encoding.GetBytes(secret);
byte[] messageBytes = encoding.GetBytes(message);
using var hmacSha256 = new HMACSHA256(keyByte);
byte[] messageHash = hmacSha256.ComputeHash(messageBytes);
var builder = new StringBuilder();
builder.Append("sha256=");
foreach (var b in messageHash)
{
builder.Append(b.ToString("x2"));
}
return builder.ToString();
}
Or take a look at the full webhook receival example build in PHP
(opens new window).
# Webhook using oAuth
With the endpoint PUT /api/v1/{partyId}/hook
in the PSB Api you can setup a hook using oAuth to deliver the webhook. To be able to use the oAuth flow the 'Init' object can be used. In this object the clientId and clientSecret can be added, along with the 'tokenUrl' and 'auth' query parameter. We will first retrieve a Bearer token before delivering the webhook to your endpoint. For this flow the webhook secret is optional, if left out we won't supply the 'X-EConnect-Signature' header as mentioned above.
{
"id": "1",
"name": "oAuth webhook",
"action": "https://accp-psb.econnect.eu/file?tokenUrl=https://accp-psb.econnect.eu/token&auth=clientCredentials#123",
"init" : {
"clientId" : "123456",
"clientSecret" : "9876543"
},
"topics": [
"InvoiceReceived",
"InvoiceSent",
"InvoiceSentError",
"InvoiceSentRetry"
],
"isActive": true
}
When the oAuth hook is being setup, we will try to retrieve a Bearer token to validate the credentials. When we aren't able to retrieve a Bearer token, an error is thrown and the hook isn't saved. Setting a the clientId and slientSecret is a one-time action. When you update the hook, for example by adding a topic, you do not have to provide the clientId and clientSecret again. The clientId and clientSecret can no longer be read back via the GET /api/v1/{partyId}/hook
.
# Webhook using mutual SSL
With the endpoint PUT /api/v1/{partyId}/hook
in the PSB Api you can setup a hook with mutual SSL authentication. To be able to use mutual SSL the 'Init' object can be used. In this object the certificate can be added as base64 encoded string:
{
"id": "1",
"name": "ssl hook",
"action": "https://econnect.eu#secretKey",
"init": {"certificate": "MIIKngIBAzCCCloGCSqGSIb3DQEHAaCCCksEggpHMIIKQzCCBgQGCSqGSIb3DQEHAaCCBfUEggXxMIIF7TCCBekGC(…)"},
"topics": [
"InvoiceReceived",
"InvoiceReceivedError",
"InvoiceSent",
"InvoiceSentError",
"InvoiceSentRetry"
],
"isActive": true
}
Setting a certificate is one-time action. The certificate is then saved when the hook is modified. When you update the hook, for example by adding a topic, you do not have to provide the certificate again. The certificate can no longer be read back via the GET /api/v1/{partyId}/hook
.
Currently PEM and PKCS # 12 (.pfx and .p12) certificates are supported. The certificate must contain the public and private key and must not be password protected.
To create a base64 encoded string from a certificate you can use OpenSSL, or the following Powershell command:
$fileContentBytes = get-content 'C:certificaat.pfx' -Encoding Byte
[System.Convert]::ToBase64String($fileContentBytes) | Out-File ‘certificaat_base64string.txt’
Extra options for webhooks
queryParam | values | description |
---|---|---|
content | file | Normally, the webhook object is posted to the supplied endpoint. Should you require the file to be posted instead, it is possible to supply the 'content' query parameter with value 'file', which is the only option supported for now. It's also possible to add the targetDocumentTypeId mentioned in the maill hooks section if necessary. |
noRetryCodes | 400,401,403 | It's possible your endpoint returns an error code you know shouldn't be retried. When the 'noRetryCodes' query parameter is used, every code supplied will be exluded from retries. When we get one of these codes, a HookSentError will be thrown. By default we are retrying everything that is not a 2xx HTTP status code. |
# Responding to a webhook
To acknowledge receipt of a webhook your endpoint should return a 2xx HTTP status code. Any response code outside of this range, including 3xx codes, will indicate that you did not receive the webhook. The default webhook timeout is 100 seconds, we expect any response code from your side within 100 seconds.
We will attempt to deliver your webhooks for up to five days, with an exponential time increase between retries. Set up a mail hook with the topic HookSentError to be notified about delivery failures. The webhooks cannot be triggered manually.
We only accept https endpoints with a valid server certificate.
Disable any CSRF protection on your webhook endpoints to avoid the webhook being blocked.
# Returned elements
Element | Description |
---|---|
topic | Topic that triggered the hook. |
partyId | The hook's owner. The party that sent or received a document. |
hookId | The hook that triggered the message. |
documentId | Id of the document that was processed. |
message | Description why the hook was triggered. |
details | A dictionary containing more information specific to the topic. |
createdOn | Original date the hook was triggered. |
sentOn | Date the hook was actually sent. |
The details element is dynamic, it is possible this will contain new fields in the future.
# Https Basic
Push invoices to our HTTPS endpoint (HTTPS with basic features).
# Configure the https endpoint (inbound or outbound)
Configuring the endpoint works much the same way as a webhook; Register using the PUT method on the hook
endpoint.
The endpoint will be available using a generated token from the result of the PUT
method.
This should only be used when the regular API is not an option (when tokens are required).
{
"id": "1",
"name": "https inbound",
"action": "httpsin://user:pass@inbound?token=$token$",
"publishTopics": [
"ReceiveInvoice"
],
"isActive": true
}
The $token$
part will be replaced with the generated token, which can then be used on our endpoint.
The endpoint will be /trigger/https/v1/$token$
where $token$
needs to be replaced with the generated token.
# Configure the https endpoint (outbound)
It's also possible to configure the https endpoint to send invoices instead of receiving them.
Only the publishTopics
should be changed to SendInvoice
or equivalents, and the action should be set to outbound.
This ensures the eConnect validation does not fail during the delivery process.
{
"id": "1",
"name": "https outbound",
"action": "httpsin://user:pass@outbound?token=$token$",
"publishTopics": [
"SendInvoice"
],
"isActive": true
}
Just like before, the $token$
part will be replaced with the generated token, which can then be used on our endpoint.
The endpoint will be /trigger/https/v1/$token$
where $token$
needs to be replaced with the generated token.
# Sending an invoice using https with basic authentication
The endpoint under the token will have been configured after the action has been set.
It is now possible to send invoices to the endpoint by POST
with the invoice in the attachment body.
Validation is done asynchronously, hence a mail hook should be configured.
If user:pass
has been set in the action, the basic authentication header will need to be present.
# Batch
Instead of handling every event one by one, it's also possible to create batches with a hook with the batch
action.
The batch
hook will collect all events with a certain topic for a given period and will then create a batch of all the documents connected to those events.
This way it's possible to for example create a ZIP file with all the received invoices from the last hour.
# Configure the batch hook
The action for the batch hook looks like this:
batch://zip?period=00:15:00&maxBatchSize=100&excludePrimaryAttachment=false&includeAdditionalAttachments=true&targetDocumentTypeId={documentTypeId}
For now the only supported batch action is zip
. This action will create a ZIP file containing all the collected files.
The period
parameter determines how long the hook waits for new events after the first event of a batch is received. If the maxBatchSize
is reached before the period
is over the hook will stop collecting new events and start with the batch action, like creating a zip file. When the batch action is done, the hook will start collecting events for the configured period
again. The default period
is 15 minutes and the default maxBatchSize
is 100.
The excludePrimaryAttachment
and includeAdditionalAttachments
can be used to influence the files that are added to zip file. By default the primary attachment (i.e. the XML invoice) will be added to the zip. includeAdditionalAttachments
can be set to true to also add the AdditionalDocumentReference’s from the XML invoice, like a PDF attachment, to the zip as separate files.
Optionally, query parameter targetDocumentTypeId
can be used to specify a target DocumentTypeId. The primary attachment will be of this documentTypeId, given a transformation from the original to the target DocumentTypeId is available. Special characters in the documentTypeId have to be escaped.
A batch hook supports only one topic at a time and publishes that topic as -Batched
after the batch action is complete. For example, a hook that batches InvoiceReceived
events publishes events with a InvoiceReceivedBatched(Error/Retry)
topic.
A full hook example looks like this:
{
"id": "1",
"name": "batch hook",
"action": "batch://zip?period=00:15:00&maxBatchSize=100&excludePrimaryAttachment=false&includeAdditionalAttachments=true&targetDocumentTypeId=urn%3Aoasis%3Anames%3Aspecification%3Aubl%3Aschema%3Axsd%3AInvoice-2%3A%3AInvoice%23%23urn%3Acen.eu%3Aen16931%3A2017%23compliant%23urn%3Afdc%3Anen.nl%3Anlcius%3Av1.0%3A%3A2.1",
"topics": [
"InvoiceReceived"
],
"isActive": true,
"createdOn": "2023-04-03T14:07:24.8813007+02:00"
}