Arlo

REST Auth API: Webhooks

This article documents how to use Arlo's Webhook implementation.

Skip to:

What are webhooks?

Webhooks allow you to subscribe to certain events that happen in Arlo. A list of the supported resources and events on those resources is given below. When one of these events is triggered we'll send a HTTP POST payload to the URL configured.

Multiple webhook endpoints can be configured for an Arlo platform, each servicing a different need (e.g. Arlo for Moodle, Arlo for Wordpress).

Supported events

Resource Event Description
Contact Created A contact has been created in Arlo
Contact Updated A contact has been updated in Arlo
Registration Created A registration has been created in Arlo
Registration Updated A registration has been updated in Arlo
Order Created An order has been created in Arlo
Order Updated An order has been updated in Arlo
Event Created An event has been created in Arlo
Event Updated An event has been updated in Arlo
OnlineActivity Created An online activity (elearning) has been created in Arlo
OnlineActivity Updated An online activity (elearning) has been updated in Arlo
EventTemplate Created An event template has been created in Arlo
EventTemplate Updated An event template has been created in Arlo
OrganisationCreated Created An organisation has been created in Arlo
OrganisationUpdated Updated An organisation has been updated in Arlo
ContactMergeRequest Created A contact merge request has been created in Arlo
OrganisationMergeRequest Created An organisation merge request has been created in Arlo
Lead Created A lead has been created in Arlo
Lead Updated A lead has been updated in Arlo
CreditNote Created A credit note has been created in Arlo
CreditNote Updated A credit note has been updated in Arlo

Payload format (JSON)

Webhook callbacks contain a HTTP POST payload containing an array of one or more platform events as a JSON-encoded structure.

Platform events describe various entity change notifications such as Contact.Created, Event.Updated, Organisation.Created, etc.

{
    "events": [
        {
            "id": "108",
            "dateTime": "2023-07-18T00:02:55.7577072Z",
            "type": "Contact.Updated",
            "resourceType": "Contact",
            "resourceId": "10987"
        },
        {
            "id": "109",
            "dateTime": "2023-07-18T00:02:56.8132012Z",
            "type": "Organisation.Updated",
            "resourceType": "Organisation",
            "resourceId": "10988"
        },
        {
            "id": "110",
            "dateTime": "2023-07-18T00:02:57.01488785Z",
            "type": "Registration.Created",
            "resourceType": "Registration",
            "resourceId": "10989"
        },
        {
            "id": "111",
            "dateTime": "2023-07-18T00:02:58.01488785Z",
            "type": "OrganisationMergeRequest.Created",
            "resourceType": "OrganisationMergeRequest",
            "resourceId": "f1d7b698-3c22-45df-9740-066f3594daa0"
        }
    ]
}
            

The payload contains only a reference by ID to the entity that was created or updated, but does not include any details of the entity. You can to query the respective endpoint (such as /auth/resources/contacts/{ContactID}, or /auth/resources/organisationmergerequests/{RequestID}) to fetch the entity details, or use the PlatformEvents endpoint to efficiently fetch multiple entities in one request.

Note that the resourceId property is always expressed as a string in the payload. It may be a string with an integer value (such as "3861799011"), or a GUID value (such as "f1d7b698-3c22-45df-9740-066f3594daa0") depending on the resourceType.

Handler implementation

Processing multiple events in one callback

Your handler should be written so it can process payloads that contain many events in a single callback. You should factor this in when writing your processing code so that you don't exceed server timeouts for HTTP request processing (such as 30 or 60 seconds in some systems). This might happen if your handler does expensive actions for certain kinds of webhook events and receives a payload from Arlo that requires many of these to be processed at once.

The maximum number of events that can be included in a single callback is 10. Additional events would be in a separate callback batch.

Store the last successfully-processed event ID

After you process each event, you should store a high watermark id value to indicate the most recent (highest numbered) event successfully processed. You can use this to track if you have missed any events. To detect if some callbacks have been missed, you can periodically query the PlatformEvents endpoint to check if there are any events after your high watermark value.

Newer events will have larger identifiers. Note that some events may be received out of order if they are repeat callbacks due to a previous failure so your code should not necessarily assume that the most recent callback is for the most recent events.

HTTP response codes

Your callback handler should return a HTTP response with a status code in the 200-299 range, usually 200 OK or 204 No Content. Any body in the HTTP response will be ignored, and will not be processed. Responses with a status code outside this range will be considered as error responses.

When Arlo receives an error response, it will automatically retry sending the payload, attempting this process up to a maximum of five times.

Callback processing time

Your webhook handler must return a response within 5000ms. Responses that take longer will be marked as a timeout, and will be considered unsuccessful.

In designing your handler, you need to consider that a webhook payload might include multiple records for you to process in one batch, and your processing logic must be able to complete the handling of that payload within 5000ms in the worst case scenario. If you have complex logic that needs to be executed or logic that might have variable performance, you will need to design a background process to store the payload and process it asynchronously so that the handler can return a status code immediately. Example implementations would be a queue, or a background thread.

Validating callbacks

Before attempting to process the webhook callback, your handler should first validate two headers: X-Arlo-Platform and X-Arlo-Signature to ensure the callback is from the expected platform, and has a valid security signature.

Your handler should not perform any actions from a callback until these two headers are verified.

  1. Validate X-Arlo-Platform header from the HTTP request contains the expected value.
  2. Retrieve the X-Arlo-Signature header from the HTTP request (called RequestClaimedSignatureBase64)
  3. Convert the callback HTTP body content to a byte array using UTF-8 encoding (called RequestPayloadUtf8Bytes)
  4. Retrieve the secret key associated with your endpoint (called SecretKeyBase64). This was provided as a Base64-encoded string when the endpoint was registered in Arlo, which should have been saved somewhere safe.
  5. Decode the secret key Base64 string into a byte array (called SecretKeyBytes)
  6. Compute the HMAC-SHA512 signature for RequestPayloadUtf8Bytes using key SecretKeyBytes. Call the result RequestPayloadHmacBytes.
  7. Convert RequestPayloadHmacBytes array into a Base64-encoded string (called ComputedRequestSignatureBase64).
  8. Compare the strings RequestClaimedSignatureBase64 and ComputedRequestSignatureBase64 using a case-sensitive compare match.

Example 1

Use the details from this example to test your validation implementation with a known secret key, a given payload and expected HMAC validation signature.

To pass the test, your own implementation should produce the same computed signature when given the same key and payload inputs.

Endpoint secret key (Base64-encoded)

elltZEpnSVBUSmx3YWJ2a3ZrbndWb0cx

Webhook callback HTTP POST request

POST https://acme.org/webhooks/handler HTTP/1.1
X-Arlo-Signature: OQjMwAcQXVIfncRRqznUsibyO3qyjJQYSKI3sAMkFO+0aumoPx1xdw8Wz2iuamfpXBtJvBrPAAIU1gOT4L0V+g==
X-Arlo-Platform: demo.arlo.co
Content-Type: application/json; charset=utf-8
Content-Length: 134

{"events":[{"id":"108","dateTime":"2023-07-18T00:02:55.7577072Z","type":"Contact.Updated","resourceType":"Contact","resourceId":"2"}]}

Computed HMAC-SHA512 signature for HTTP request (Base64-encoded)

OQjMwAcQXVIfncRRqznUsibyO3qyjJQYSKI3sAMkFO+0aumoPx1xdw8Wz2iuamfpXBtJvBrPAAIU1gOT4L0V+g==

This computed signature string should match the X-Arlo-Signature header value.

Example validation implementation (JavaScript)

This example uses the JavaScript native WebCrypto API and is suitable for use with NodeJS 15+, but also demonstrates a basic implementation that you could convert for other platforms such as PHP or C#.

NOTE: The details from this example are for demonstration and testing purposes. You should put the secret key in a safe place such as a cloud secret or key storage vault rather than hardcoding it in your application.

Separate to validating the X-Arlo-Signature header, you should ensure you validate that the X-Arlo-Platform value contains a known value (not illustrated in this example).

async function computeContentHmacSignatureBase64(contentString, secretKeyBase64) {
    let utf8Encoder = new TextEncoder("utf-8");
    let hmacSha512Algorithm = { name: "HMAC", hash: "SHA-512" };

    let secretKeyString = atob(secretKeyBase64);
    let secretKeyBytes = utf8Encoder.encode(secretKeyString);
    let contentUtf8Bytes = utf8Encoder.encode(contentString);

    let cryptoKey = await crypto.subtle.importKey(
        "raw",
        secretKeyBytes,
        hmacSha512Algorithm,
        false,
        ["sign", "verify"]
    );

    let hmacSignatureBytes = await crypto.subtle.sign(
        hmacSha512Algorithm.name,
        cryptoKey,
        contentUtf8Bytes
    );

    let hmacSignatureBase64 = btoa(
        String.fromCharCode(...new Uint8Array(hmacSignatureBytes))
    );
    return hmacSignatureBase64;
}
    
(async () => {
    "use strict";

    //Endpoint secret key string saved when endpoint was registered
    let endpointSecretKeyBase64 = "elltZEpnSVBUSmx3YWJ2a3ZrbndWb0cx"; 

    //Signature extracted from HTTP request "X-Arlo-Signature" header
    let validationSignatureBase64 = "OQjMwAcQXVIfncRRqznUsibyO3qyjJQYSKI3sAMkFO+0aumoPx1xdw8Wz2iuamfpXBtJvBrPAAIU1gOT4L0V+g==";

    //Content from HTTP request body
    let callbackPayloadString =
        '{"events":[{"id":"108","dateTime":"2023-07-18T00:02:55.7577072Z","type":"Contact.Updated","resourceType":"Contact","resourceId":"2"}]}';
    //Compute HMAC signature for the content
    let computedPayloadSignatureBase64 = await computeContentHmacSignatureBase64(
        callbackPayloadString,
        endpointSecretKeyBase64
    );

    let isValidSignature = validationSignatureBase64 === computedPayloadSignatureBase64;

    console.log("Is valid: " + isValidSignature);
})();
            

Efficiently fetching related entities

While it is possible to process each event and retrieve the entity separately from the various endpoints (Contacts, Organisations, Registrations, etc), we do provide an optimised endpoint to allow you to retrieve all of the events and related entities for a batch in a single request. This can increase the performance of your handler and is strongly recommended.

For example, given this webhook payload with three separate events.

{
    "events": [
        {
            "id": "108",
            "dateTime": "2023-07-18T00:02:55.7577072Z",
            "type": "Contact.Updated",
            "resourceType": "Contact",
            "resourceId": "10987"
        },
        {
            "id": "109",
            "dateTime": "2023-07-18T00:02:56.8132012Z",
            "type": "Organisation.Updated",
            "resourceType": "Organisation",
            "resourceId": "10988"
        },
        {
            "id": "110",
            "dateTime": "2023-07-18T00:02:57.01488785Z",
            "type": "Registration.Created",
            "resourceType": "Registration",
            "resourceId": "10989"
        }
    ]
}
            

The event IDs in this callback are 108, 109 and 110, each referencing different kinds of entities (Contacts, Organisations and Registrations).

The entities associated with these events can be efficiently retrieved in a single call using the PlatformEvents collection endpoint:

/api/2012-02-01/auth/resources/platformevents/query/?ids={comma-separated list of IDs}&expand=PlatformEvent,PlatformEvent/Link

Depending on the complexity of your handler, you may want to query the resource endpoints directly if you have complex expand requirements. For many scenarios, this optimised endpoint is sufficient for many cases and enables you to avoid making multiple API queries to fetch additional entities.

Example

/api/2012-02-01/auth/resources/platformevents/query/?ids=108,109,110&expand=PlatformEvent,PlatformEvent/Link

<PlatformEvents>
    <Link href="https://demo.arlo.co/api/2012-02-01/auth/resources/platformevents/108/" rel="http://schemas.arlo.co/api/2012/02/auth/related/PlatformEvent" title="PlatformEvent" type="application/xml">
        <PlatformEvent>
            <ID>108</ID>
            <Type>Updated</Type>
            <ResourceType>Contact</ResourceType>
            <ResourceID>10987</ResourceID>
            <Link href="https://demo.arlo.co/api/2012-02-01/auth/resources/platformevents/108/" rel="self" type="application/xml"/>
            <Link href="https://demo.arlo.co/api/2012-02-01/auth/resources/contacts/10987/" rel="http://schemas.arlo.co/api/2012/02/auth/related/Link" title="Link" type="application/xml">
                <Contact>
                    <ContactID>10987</ContactID>
                    <UniqueIdentifier>2af1573a-6227-14d9-0fe2-add46279113f</UniqueIdentifier>
                    <FirstName>Barry</FirstName>
                    <LastName>Jones</LastName>
                    <Email>barry@example.org</Email>
                    <CodePrimary>JONESBA01</CodePrimary>
                    <PhoneWork>04 9700 19332</PhoneWork>
                    <PhoneHome>04 9712 00234</PhoneHome>
                    <PhoneMobile>021 1234 56890</PhoneMobile>
                    <Status>Active</Status>
                    <CreatedDateTime>2011-10-17T03:51:29.577Z</CreatedDateTime>
                    <LastModifiedDateTime>2011-10-19T02:55:42.412Z</LastModifiedDateTime>
                    <Link rel="self" type="application/xml" href="https://demo.arlo.co/api/2012-02-01/auth/resources/contacts/10987/"/>
                    <Link rel="http://schemas.arlo.co/api/2012/02/auth/related/CustomFields" type="application/xml" title="CustomFields" href="https://demo.arlo.co/api/2012-02-01/auth/resources/contacts/10987/customfields/"/>
                    <Link rel="http://schemas.arlo.co/api/2012/02/auth/related/PostalAddress" type="application/xml" title="PostalAddress" href="https://demo.arlo.co/api/2012-02-01/auth/resources/contacts/10987/postaladdress/"/>
                    <Link rel="http://schemas.arlo.co/api/2012/02/auth/related/Employment" type="application/xml" title="Employment" href="https://demo.arlo.co/api/2012-02-01/auth/resources/contacts/10987/employment/"/>
                    <Link rel="http://schemas.arlo.co/api/2012/02/auth/related/TimeZone" type="application/xml" title="TimeZone" href="https://demo.arlo.co/api/2012-02-01/auth/resources/timezones/2/"/>
                    <Link rel="http://schemas.arlo.co/api/2012/02/auth/related/IntegrationData" type="application/xml" title="IntegrationData" href="https://demo.arlo.co/api/2012-02-01/auth/resources/contacts/10987/integrationdata"/>
                    <Link rel="http://schemas.arlo.co/api/2012/02/auth/related/Registrations" type="application/xml" title="Registrations" href="https://demo.arlo.co/api/2012-02-01/auth/resources/contacts/10987/registrations/"/>
                    <Link rel="http://schemas.arlo.co/api/2012/02/auth/related/Regions" type="application/xml" title="Regions" href="https://demo.arlo.co/api/2012-02-01/auth/resources/contacts/10987/regions/"/>
                    <Link rel="http://schemas.arlo.co/api/2012/02/auth/related/Marketing" type="application/xml" title="Marketing" href="https://demo.arlo.co/api/2012-02-01/auth/resources/contacts/10987/marketing/"/>
                    <Link rel="http://schemas.arlo.co/api/2012/02/auth/related/PresenterIdentity" type="application/xml" title="PresenterIdentity" href="https://demo.arlo.co/api/2012-02-01/auth/resources/contacts/10987/presenteridentity/"/>
                    <Link rel="http://schemas.arlo.co/api/2012/02/auth/related/Security" type="application/xml" title="Security" href="https://demo.arlo.co/api/2012-02-01/auth/resources/contacts/10987/security/"/>
                </Contact>
            </Link>
            <Link href="https://demo.arlo.co/api/2012-02-01/auth/resources/contacts/10987/" rel="http://schemas.arlo.co/api/2012/02/auth/related/Contact" title="Contact" type="application/xml"/>
        </PlatformEvent>
    </Link>
    <Link href="https://demo.arlo.co/api/2012-02-01/auth/resources/platformevents/109/" rel="http://schemas.arlo.co/api/2012/02/auth/related/PlatformEvent" title="PlatformEvent" type="application/xml">
        <PlatformEvent>
            <ID>109</ID>
            <Type>Updated</Type>
            <ResourceType>Organisation</ResourceType>
            <ResourceID>10988</ResourceID>
            <Link href="https://demo.arlo.co/api/2012-02-01/auth/resources/platformevents/109/" rel="self" type="application/xml"/>
            <Link href="https://demo.arlo.co/api/2012-02-01/auth/resources/organisations/10988/" rel="http://schemas.arlo.co/api/2012/02/auth/related/Link" title="Link" type="application/xml">
                <Organisation>
                    <OrganisationID>10988</OrganisationID>
                    <Name>Acme Consultants</Name>
                    <LegalName>Acme Consultants Limited</LegalName>
                    <Email>admin@acme.example.org</Email>
                    <CodePrimary>ACMECONSUL04</CodePrimary>
                    <CodeSecondary>74-582-821</CodeSecondary>
                    <PhonePrimary>+64 4 211 2334</PhonePrimary>
                    <PhoneSecondary>+64 4 211 2334 ext 231</PhoneSecondary>
                    <WebsiteUrl>acme.example.org</WebsiteUrl>
                    <Status>Active</Status>
                    <CreatedDateTime>2009-11-23T02:49:59.493Z</CreatedDateTime>
                    <LastModifiedDateTime>2009-11-25T02:11:32.174Z</LastModifiedDateTime>
                    <Link rel="self" type="application/xml" href="https://demo.arlo.co/api/2012-02-01/auth/resources/organisations/10988/"/>
                    <Link rel="http://schemas.arlo.co/api/2012/02/auth/related/PostalAddress" type="application/xml" title="PostalAddress" href="https://demo.arlo.co/api/2012-02-01/auth/resources/organisations/10988/postaladdress/"/>
                    <Link rel="http://schemas.arlo.co/api/2012/02/auth/related/AccountManager" type="application/xml" title="AccountManager" href="https://demo.arlo.co/api/2012-02-01/auth/resources/contacts/32/"/>
                    <Link rel="http://schemas.arlo.co/api/2012/02/auth/related/KeyContact" type="application/xml" title="KeyContact" href="https://demo.arlo.co/api/2012-02-01/auth/resources/contacts/683/"/>
                    <Link rel="http://schemas.arlo.co/api/2012/02/auth/related/ChildOrganisations" type="application/xml" title="ChildOrganisations" href="https://demo.arlo.co/api/2012-02-01/auth/resources/organisations/10988/childorganisations/"/>
                </Organisation>
            </Link>
            <Link href="https://demo.arlo.co/api/2012-02-01/auth/resources/organisations/10988/" rel="http://schemas.arlo.co/api/2012/02/auth/related/Organisation" title="Organisation" type="application/xml"/>
        </PlatformEvent>
    </Link>
    <Link href="https://demo.arlo.co/api/2012-02-01/auth/resources/platformevents/110/" rel="http://schemas.arlo.co/api/2012/02/auth/related/PlatformEvent" title="PlatformEvent" type="application/xml">
        <PlatformEvent>
            <ID>110</ID>
            <Type>Created</Type>
            <ResourceType>Registration</ResourceType>
            <ResourceID>10989</ResourceID>
            <Link href="https://demo.arlo.co/api/2012-02-01/auth/resources/platformevents/110/" rel="self" type="application/xml"/>
            <Link href="https://demo.arlo.co/api/2012-02-01/auth/resources/registrations/10989/" rel="http://schemas.arlo.co/api/2012/02/auth/related/Link" title="Link" type="application/xml">
                <Registration>
                    <RegistrationID>10989</RegistrationID>
                    <UniqueIdentifier>730dd1df-4a0c-4660-9267-51670807b843</UniqueIdentifier>
                    <Attendance>Attended</Attendance>
                    <Outcome>Pass</Outcome>
                    <Grade>A+ (92%)</Grade>
                    <Status>Approved</Status>
                    <ProgressPercent>75.0</ProgressPercent>
                    <ProgressStatus>Module 4B in progress</ProgressStatus>
                    <Status>Completed</Status>
                    <CertificateSentDateTime>2018-05-29T00:20:11.3165591Z</CertificateSentDateTime>
                    <CreatedDateTime>2011-10-28T22:42:13.95Z</CreatedDateTime>
                    <LastModifiedDateTime>2011-03-23T09:25:09.483Z</LastModifiedDateTime>
                    <Link rel="self" type="application/xml" href="https://demo.arlo.co/api/2012-02-01/auth/resources/registrations/10989/"/>
                    <Link rel="http://schemas.arlo.co/api/2012/02/auth/related/CustomFields" type="application/xml" title="CustomFields" href="https://demo.arlo.co/api/2012-02-01/auth/resources/registrations/10989/customfields/"/>
                    <Link rel="http://schemas.arlo.co/api/2012/02/auth/related/Event" type="application/xml" title="Event" href="https://demo.arlo.co/api/2012-02-01/auth/resources/events/176/"/>
                    <Link rel="http://schemas.arlo.co/api/2012/02/auth/related/Contact" type="application/xml" title="Contact" href="https://demo.arlo.co/api/2012-02-01/auth/resources/contacts/872/"/>
                    <Link rel="http://schemas.arlo.co/api/2012/02/auth/related/SourceInfo" type="application/xml" title="SourceInfo" href="https://demo.arlo.co/api/2012-02-01/auth/resources/registrations/10989/sourceinfo/"/>
                    <Link rel="http://schemas.arlo.co/api/2012/02/auth/related/SessionRegistrations" type="application/xml" title="SessionRegistrations" href="https://demo.arlo.co/api/2012-02-01/auth/resources/registrations/10989/sessionregistrations/"/>
                    <Link rel="http://schemas.arlo.co/api/2012/02/auth/related/OptionRegistrations" type="application/xml" title="OptionRegistrations" href="https://demo.arlo.co/api/2012-02-01/auth/resources/registrations/10989/optionregistrations/"/>
                    <Link rel="http://schemas.arlo.co/api/2012/02/auth/related/OrderLine" type="application/xml" title="OrderLine" href="https://demo.arlo.co/api/2012-02-01/auth/resources/orderlines/23990/"/>
                </Registration>
            </Link>
            <Link href="https://demo.arlo.co/api/2012-02-01/auth/resources/registrations/10989/" rel="http://schemas.arlo.co/api/2012/02/auth/related/Registration" title="Registration" type="application/xml"/>
        </PlatformEvent>
    </Link>
    <Link href="https://demo.arlo.co/api/2012-02-01/auth/resources/platformevents/" rel="self" type="application/xml"/>
</PlatformEvents>

Synchronising missed events

Recent events can be accessed from the PlatformEvents query endpoint:

/auth/resources/platformevents/query

You can use this to check if some webhook callbacks may have been missed such as after an outage or failure to process certain events, or if you want to proactively replay some events from the past.

If you track the last event ID you have processed and want to check if there are new events you might have missed, you can use this endpoint to retrieve all events after that using the afterID filter:

/api/2012-02-01/auth/resources/platformevents/query/?afterID=10033