Getting started with webhooks
Learn how to use OfficeRnD Webhooks infrastructure and APIs.
Webhooks allow developers to build integrations, which are notified when a certain event happens in OfficeRnD. When such an event is triggered, the webhook's configured URL will receive an HTTP POST request. Webhooks can be used to update a dataset in a local service deployment; to notify the change of a linked entity in another 3rd party service; or send messages to a set of email addresses; and more.
What are webhooks
Webhooks are a communication mechanism that delivers real-time notifications whenever a given event happens. The webhooks automatically 'push' a notification immediately when an event in the system occurs. In comparison, integration implementations can also poll (a 'pull' communication method) - periodically check for data additions or changes.
Important
We do not recommend polling as a viable approach to periodically pull large datasets. Normally changes in the data occur to a handful of entries and not to all. With that in mind, it is suboptimal to pull an entire dataset instead of iteratively applying the changes as they happen in OfficeRnD. Additionally, polling requests can hit the rate limit and be cut off.
The webhook has an associated URL (i.e., https://your.integration.url/webhooks). The notification payloads are delivered to the URL using HTTP POST requests. The webhook notification payload contains all relevant information about what happened in OfficeRnD, including the type of event and the data changes associated with that event. The webhook receiver uses the event details and decides what to do with it - such as deactivating the associated Salesforce entity.
Register Webhook
Developers can register webhooks that deliver notifications for one, a few, or for all events in their OfficeRnD account.
As a prerequisite to registering a webhook in OfficeRnD, you need to build your own custom endpoint on your server. Additionally, you must ensure the server URL is secure (i.e. it begins with https://), otherwise, you won't be allowed to register the webhook endpoint.
Following is an example code of a simple HTTP Node.js server that listens for POST requests at the /webhooks/callback endpoint.
const app = require('express')();
const bodyParser = require('body-parser');
app.use(bodyParser.raw({ type: 'application/json' }));
app.post('/webhooks/callback', (req, res) => {
const { body, rawBody, headers } = req;
const { eventType, data } = body;
switch (eventType) {
case 'company.paymentdetails.removed':
// ...
break;
case 'member.paymentdetails.removed':
// ...
break;
case 'member.removed':
case 'company.removed':
// ...
break;
default:
// unhandled event
console.log(`Event ${eventType} was not handled.`);
}
}
app.listen(3000, () => console.log('Listening on 3000'));
You can find the complete node.js example in the webhooks-demo GitHub repo.
Note
The example above is purely for illustrative purposes. The technology of choice and server implementation details may differ greatly.
Register the webhook in Settings > Developer Tools > Webhooks. You'll be asked for the following:
- Endpoint URL - the endpoint of your server where HTTP POST requests will be made
- Description - an optional description allowing you to describe the purpose of the webhook
- Events to send - the list of events for which requests will be made to the endpoint
Receiving Webhook notifications
For each event occurrence, the webhook is registered for, the webhook data is POSTed to your endpoint in JSON format.
The webhook notification request payload will be a JSON comprising the following fields:
Property | Type | Description |
---|---|---|
event | string | The unique identifier of the event instance |
eventType | string (system event type) | The event type that occurred in OfficeRnD |
data | object | The event payload describing the event |
data.object | object | The resource that has been added/changed/removed |
data.previousAttributes | object | The field is only present for updated events. The object describing all changed attributes and their previous values |
data.<parent-resource-name> | string | The field is only present for subresource events to identify the parent resource - e.g. data.invoice , data.member , etc. |
createdAt | string (ISO Date) | The time of the event occurrence. |
Below is an example of a webhook notification delivered when the payment card details of a company are deleted:
{
"event": "5f8d6c9747120500116899e4",
"eventType": "company.paymentdetails.removed",
"data": {
"object": {
"_id": "5f86de9c1e74e400d416467c",
"card": {
"id": "card_<redacted>",
"name": "E Corp.",
"brand": "Visa",
"funding": "credit",
"exp_month": 11,
"exp_year": 2022,
"country": "US",
"cvc_check": "pass",
"last4": "1111"
},
"provider": "stripe",
"providerId": "<redacted>",
"sourceId": "card_<redacted>",
"authorization": {
"authProviderId": "seti_<redacted>",
"status": "not_required"
},
"createdAt": "2020-10-14T11:18:52.130Z",
"createdBy": "5e131db18edb280010c8d4ef"
},
"company": "5e1d8bd37f8759001051b0e8"
},
"createdAt": "2020-10-19T10:38:15.127Z"
}
Similarly, when a member is removed, a webhook notification for the member.removed
event is delivered:
{
"event": "5f8715ca1e74e400d4164ea6",
"eventType": "member.removed",
"data": {
"object": {
"_id": "5e8c9df14d46c7001064c3ef",
"status": "active",
"calculatedStatus": "drop-in",
"team": "5e8c9eb94d46c7001064c427",
"office": "5e1d8bd37f8759001051b0e2",
"name": "Jonathan Brown",
"description": "",
"image": null,
"organization": "5e1d8bd37f8759001051b0ca",
"email": "[email protected]",
}
},
"createdAt": "2020-10-14T15:14:18.042Z"
}
Responding to webhook notifications
To acknowledge that a notification has been received, your endpoint must respond with a 2xx
HTTP status code to OfficeRnD. Sending a response body is not required.
Additionally to the response status code, your endpoint must also respond quickly - prior to running any complex logic upon receipt of a notification, ensure you've returned a 2xx
HTTP status code.
If your endpoint doesn't respond accordingly with the correct status code or in a timely manner, the webhook notification attempt will be treated as a failure.
Important
Currently failed webhook notification attempts will not be automatically retried.
Test your webhooks
After configuring your server to be able to receive POST requests, and registering your webhook in OfficeRnD, it's time to test it. To that end, OfficeRnD's webhook details view provides you with a list of all webhook notification delivery attempts.
As your endpoint receives notification payloads asynchronously, its failures may not be immediately obvious. We generally recommend verifying that the webhook notification attempts are successful after making any changes to the server.
Secure your webhooks
Once your server is configured to receive notification payloads, it'll be open for any requests sent to it. For security reasons, you should verify all incoming requests and allow only those coming from OfficeRnD. All webhook notification payloads sent by OfficeRnD are signed and the signature is included in the officernd-signature
header of the request. Using the webhook's secret
and the payload body you should verify the computed signature matches that in the header.
Webhook secrets
Before you can verify signatures, you need to retrieve your endpointβs secret from your Webhook's details view. Select a webhook that you want to obtain the secret for, then click the Click to reveal
button.
Note
If you create a webhook by calling the public API, the response from your POST request will contain the secret.
In order to get it again after you've created the webhook you can use ?$select=secret, when calling the webhooks endpoint.
Webhook secrets are unique for each registered webhook endpoint. If you register several webhooks, you will need to verify the signatures for each with their respective webhook's secret.
Verifying payloads from OfficeRnD
OfficeRnD creates a hash signature with each payload. The signature is included in the headers of each webhook notification request as officernd-signature
. For extra security, in addition to the signature, each header contains a timestamp (prefixed by t=
) which is used in the hash computation.
The officernd-signature
header has two fields, separated with ,
: t
and signature
An officernd-signature
header will look similar to this:
officernd-signature: t=1602237976,signature=099618786b221c0bd88346a71e01c8deec60007c2db2ead5975284d4e957b8f5
Signatures are computed using an HMAC hex digest with SHA-256. The raw request body and timestamp are joined by a dot .
and HMAC hashed. In order to verify the signature, you will have to repeat the process with the incoming data and compare your result to the signature in the header.
-
Extract the timestamp and signatures
- Split the header using the
,
as the separator. - Then split the resulting elements (
t=....
andsignature=...
) using the=
character as the separator. This will give you two pairs to work with - the timestamp and its value, and the signature and its hash. - Verify the payload timestamp and the current timestamp difference is within your tolerance
- Split the header using the
-
Prepare the payload to sign. Concatenate the pieces in the following order:
- The raw body payload (as a string)
- The dot character
.
- The timestamp value
-
Compute the expected signature
- Compute an HMAC with the SHA-256 hash function
- Use the webhook secret as the key
- Use the concatenated string from step 2 as the message
-
Compare the signatures
- Compare the header signature from step 1 to the expected signature from step 3
You can find a complete example of the signature verification flow in the webhooks-demo GitHub repo
System event types
Below is a complete list of all types of events OfficeRnD can currently send webhook notifications for.
All event names follow a common pattern resource.(optional)subresource.operation
such as invoice.created
or invoice.charge.created
. The supported operations are CUD - created
, updated
, deleted
. This way it should be easy to understand what an event name corresponds to within OfficeRnD.
resource | operations | events | description |
---|---|---|---|
company | created updated removed | company.created company.updated company.removed | Occurs when a company is created, updated or removed. |
company.paymentdetails | created removed | company.paymentdetails.created company.paymentdetails.removed | Occurs when payment details (i.e. card details, bank details, etc.) are added or removed for a company. |
member | created updated removed | member.created member.updated member.removed | Occurs when a member is created, updated or removed. |
member.paymentdetails | created removed | Occurs when payment details (i.e. card details, bank details, etc.) are added or removed for a member. | |
invoice | created updated removed | invoice.created invoice.updated invoice.removed | Occurs when an Invoice, Credit Note or Overpayment is created, updated, or removed. Property data.object.documentType contains information about the type of document. |
invoice.charge | created updated removed | invoice.charge.created invoice.charge.updated invoice.charge.removed | Occurs when an Invoice receives a payment, when the payment status is updated, or when the payment is manually removed. |
invoice.allocation | created removed | invoice.allocation.created invoice.allocation.removed | Occurs when an Invoice is allocated a credit from a credit note, or when an allocation is manually removed. |
fee | created updated removed | fee.created fee.updated fee.removed | Occurs when a fee is created, updated or removed. |
membership | created updated removed | membership.created membership.updated membership.removed | Occurs when a membership is created, updated or removed. |
booking | created updated removed | booking.created booking.updated booking.removed | Occurs when a booking is created, updated or removed. |
contract | created updated removed | contract.created contract.updated contract.removed | Occurs when a contract is created, updated or removed. |
ticket | created updated removed | ticket.created ticket.updated ticket.removed | Occurs when an issue is created, updated or removed. |
integration | removed | integration.removed | Occurs when an integration is disconnected from the OfficeRnD organisation. |
pass | created updated | pass.created pass.updated | Occurs when a day pass is either created or updated for a company/member. |
occupancyslot | created removed | occupancyslot.created occupancyslot.removed | Occurs when a booking is created in order to shows its occurrences. The difference between the endpoint for bookings and this one is that the occupancy slots are explicitly for occurrences - useful for recurring bookings, that have more than one occurrence in order to track the slots, that are occupied by said booking. |
Updated 10 months ago