Webhooks
Webhooks are a way of transmitting events via HTTPS from the source system (RaiseNow) to the back-end of a target web application (e.g. a customer's web server).
The great advantage of webhooks is that they make it possible for remote systems to be actively notified when something happens, instead of having to poll for changes. For instance, a system tracking successful payments can choose to receive a webhook every time the event payments.payment.succeeded
is fired in RaiseNow.
Webhooks are sent as POST requests over HTTPS. The body is a JSON object structured as an envelope, with details on the event itself, and a data
field that contains the full serialised object.
{
"event": {
"id": <uuid>,
"timestamp": <int>,
"name": <string>,
"object": <string>,
"object_uuid": <uuid>,
"organisation_uuid": <uuid>,
"account_uuid": <uuid>,
"securityContext": {
"subject": <uuid>,
"organisation": <uuid>,
"email": <string>
},
"data": <object>
}
}
id
: UUID of the event itselfname
: Full name of the event, likepayments.payment.succeeded
timestamp
: UNIX timestamp in millisecondsobject
: Name of the object involved, likepayment
organisation_uuid
: UUID of the organisation to which the object belongs, when applicableaccount_uuid
: UUID of the organisation to which the object belongs, when applicablesecurityContext
: Internal details about the user or API client that triggered the eventdata
: The serialised object to which the event refers
View the available events on the RaiseNow Events page.
Subscribing to an event via a webhook
Subscribing to events via webhooks in RaiseNow involves two steps:
- Specifying the endpoint on your system that will receive the webhooks.
- Creating an event subscription to specify which kind of events will be sent to the endpoint.
An endpoint can be configured with the Create Webhook Endpoint request, the event subscription with the Create Event Subscription request.
When registering event subscriptions through Event Subscriptions you currently need to supply the event names in a slightly adjusted form:
rnw.event.payment_gateway.payment.succeeded
rnw.event.payment_gateway.payment.failed
rnw.event.subscription_service.subscription.created
rnw.event.subscription_service.subscription.activated
rnw.event.subscription_service.subscription.suspended
rnw.event.subscription_service.subscription.cancelled
A single endpoint can be bound to different event types by having multiple event subscriptions.
The endpoint response body is irrelevant, but it should be smaller than 1 kB.
It is recommended that endpoints respond as quickly as possible. If slow operations are involved, webhooks should be stored locally and processed in a second time.
How to filter events
When creating an event subscription you might have the requirement to filter events based on the body of the event. To achieve this you can use the filter
property of a Event Subscription.
The filter
property is a string that defines the logic weather the event should be passed to the webhook or not. You can find examples of event bodies here Events.
Examples:
event.pathExists("supporter_snapshot.address_addendum", event.data)
!event.data["payment_method"] === "card"
(event.pathExists("some.path.field", event.data) && event.data["brand_code"] === "VISA") || event.data["amount"] in 1800..4500
Notice that whenever we want to target a specific field in the body of the event we have to refer to it as event.data["field"]
. An example for the raisenow.payments.payment.succeeded
event would be event.data["amount"] > 1000 && event.data["currency_identifier"] == "CHF"
.
When using the event.pathExists()
function the starting point for the path is the root of the event body (event.data
), additionally you can use .
to access nested fields.
Failures and retry mechanism
If the endpoint returns a status code in the 2xx range, the webhook is considered to have been delivered successfully. Redirect responses (3xx) will not be followed, so the webhook may be lost. 4xx-5xx status codes, network errors and timeouts will result in the retry mechanism kicking in.
Retries are attempted up to 20 times with increasing delays, from 1 minute to 12 hours. The strategy may change without notification.
The retry mechanism can be disabled by adding the header x-rnw-no-retry: true
to the response of an endpoint.
Manual retries
Coming soon
Checking message authenticity
In order to ensure that the webhooks are really coming from RaiseNow, it is possible to enable Hash-based message authentication code (HMAC) on the endpoints.
If the field hmac_key
is set on an endpoint, the system will use it to compute the SHA-512 HMAC of the JSON-serialised body and write it in the X-Hmac
header of every webhook. When receiving the webhook, the endpoint handler can use the same key to compute the HMAC of the received body, and compare it to the value in the header. If they match, the webhook came from RaiseNow and has not been tampered with.
Choosing between HTTP Basic authentication and HMAC
Trust between RaiseNow and the receiving system can be established by using HTTP Basic authentication over HTTPS, HMAC or both.
HTTP Basic authentication can often be handled by the endpoint's webserver itself or as an out-of-the-box feature of most web application frameworks, so it should be easy to set up, robust and lightweight. Since the data transfer is done via HTTPS, it is not normally possible to intercept the credentials. However, if the endpoint lies behind reverse proxies where the HTTPS connection terminates, it may be possible for an attacker to gain access to the credentials.
Implementing HMAC checks on the endpoint can be more complex, consume more system resources and, if custom code is used, it can be subject to bugs. On the other hand, since the key never leaves the two systems it is more difficult to steal it, and in some cases it is not possible to add HTTP Basic authentication.
In any case, secrets (HMAC key, HTTP credentials) must be hard to guess and stored securely.
How to validate HMAC signature
In order to validate the HMAC signature you will need the hmac_key
that you specified while creating the endpoint.
It is important to use base64
encoding and SHA-512
algorithm when calculating the signature in order to match the value provided in the x-hmac
header.
- JavaScript
- PHP
import {createHmac} from 'crypto';
({body, headers}) => {
if (
headers['x-hmac'] !==
createHmac('SHA-512', secret).update(body).digest('base64')
)
throw 'HMAC signature verification failed';
};
<?php
$payload = '... event';
$secret = 'PUT_YOUR_HMAC_KEY_HERE';
$algo = 'sha512';
$expectedHmac = 'THE_VALUE_FROM_X-Hmac_header';
$actualHmac = base64_encode(
hash_hmac(
$algo,
$payload,
$secret,
true
)
);
if ($actualHmac !== $expectedHmac){
throw new Exception('HMAC signature verification failed')
}
...