Skip to main content

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 itself
  • name: Full name of the event, like payments.payment.succeeded
  • timestamp: UNIX timestamp in milliseconds
  • object: Name of the object involved, like payment
  • organisation_uuid: UUID of the organisation to which the object belongs, when applicable
  • account_uuid: UUID of the organisation to which the object belongs, when applicable
  • securityContext: Internal details about the user or API client that triggered the event
  • data: 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:

  1. Specifying the endpoint on your system that will receive the webhooks.
  2. 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.

info

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.

import {createHmac} from 'crypto';

({body, headers}) => {
if (
headers['x-hmac'] !==
createHmac('SHA-512', secret).update(body).digest('base64')
)
throw 'HMAC signature verification failed';
};