Arlo

REST Auth API: Webhooks

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

Topics:

What are webhooks?

Webhooks allow you to subscribe to certain supported events that happen in Arlo. When one of these events is triggered we'll send a HTTP POST payload to the URL configured.

Webhooks are the recommended way to keep an external system in sync with Arlo. Without webhooks, an integration must repeatedly poll the API at regular intervals to check for changes to contacts, organisations, registrations and other resources. This approach is inefficient — most polling requests return no new data — and introduces a delay between when a change occurs in Arlo and when the external system becomes aware of it.

With webhooks, Arlo notifies your system as soon as a change happens, eliminating unnecessary API calls and ensuring your data stays up to date in near real-time.

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

The type value in the webhook payload identifies the event. The following event types are supported:

Event type 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 updated in Arlo
Organisation.Created An organisation has been created in Arlo
Organisation.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.

Fetching events and related entities as a batch

Once you have received a webhook payload that contains a list of webhook events, you can efficiently fetch the related entities (Contacts, Registrations, etc) using the PlatformEvents query endpoint.

While it is possible to process each event and retrieve the entity separately from the various endpoints, 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 and enables you to avoid making multiple API queries to fetch additional entities.

See PlatformEvents query endpoint for more details.

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>

Handler implementation best practices

Validate the callback first

Before processing any events, your handler should validate the callback by checking the X-Arlo-Platform and X-Arlo-Signature headers. Discard any callback that fails validation without processing its events.

Process multiple events in one callback

A single webhook callback can contain up to 10 events. Your handler must be able to process all events in the batch within the response time limit. Additional events beyond 10 will be delivered in a separate callback batch.

Make your handler idempotent

Your handler may receive the same event more than once. This can happen when Arlo retries a callback after a timeout or error response, or when events are replayed via the PlatformEvents query endpoint.

To handle this safely, track the id of each event you have successfully processed and skip any event whose id has already been seen. This ensures that duplicate deliveries do not cause unintended side effects such as creating duplicate records or sending duplicate notifications.

Store the last successfully-processed event ID

Each event in a webhook payload has an id that increases over time. After you successfully process each event, persist the highest id value you have handled — commonly called a high watermark. Store this value durably (for example, in a database) so that it survives application restarts.

This high watermark gives you a reliable checkpoint of how far your integration has processed, which is useful for monitoring and diagnostics.

When updating the high watermark, note that callbacks may arrive out of order — for example, when Arlo retries a previously failed batch. Update the stored value only if the new id is greater than the current high watermark, rather than always overwriting it with the last event received.

The event id is a 64-bit integer value and may exceed 2,147,483,647 (the maximum for a 32-bit integer). Ensure you use an appropriate data type such as long (C#), bigint (SQL), or equivalent when storing or comparing event IDs.

Return HTTP 2xx responses

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.

Handle partial failures

If your handler successfully processes some events in a batch but fails on others, return a non-2xx status code so that Arlo retries the entire batch. Because your handler is idempotent, the events that were already processed will be safely skipped on the retry, and only the previously failed events will be acted on.

HTTP response time matters

Your webhook handler must return a response within 5000ms. Responses that take longer will be marked as a timeout and will be considered unsuccessful. A webhook payload can contain up to 10 events, and your handler must be able to complete processing within this time limit regardless of how many events are in the batch.

Your handler should use one of the following strategies:

  • Background processing (recommended): Store the webhook payload immediately, return a 200 OK response, and process the events asynchronously using a queue or background thread. This ensures the handler always responds within the time limit, even when processing logic is complex or has variable performance.
  • Inline processing: Process all events synchronously before returning a response. This approach is simpler but requires that your processing logic can reliably handle up to 10 events within 5000ms under worst-case conditions.

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

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.

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);
})();
            

Example validation implementation (C#)

This example uses System.Security.Cryptography.HMACSHA512 and is compatible with .NET Framework 4.5+ and .NET 6+.

using System;
using System.Security.Cryptography;
using System.Text;

class Program
{
    static string ComputeContentHmacSignatureBase64(string contentString, string secretKeyBase64)
    {
        byte[] secretKeyBytes = Convert.FromBase64String(secretKeyBase64);
        byte[] contentUtf8Bytes = Encoding.UTF8.GetBytes(contentString);

        using (var hmac = new HMACSHA512(secretKeyBytes))
        {
            byte[] hmacSignatureBytes = hmac.ComputeHash(contentUtf8Bytes);
            return Convert.ToBase64String(hmacSignatureBytes);
        }
    }

    static void Main()
    {
        // Endpoint secret key string saved when endpoint was registered
        string endpointSecretKeyBase64 = "elltZEpnSVBUSmx3YWJ2a3ZrbndWb0cx";

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

        // Content from HTTP request body
        string callbackPayloadString =
            "{\"events\":[{\"id\":\"108\",\"dateTime\":\"2023-07-18T00:02:55.7577072Z\",\"type\":\"Contact.Updated\",\"resourceType\":\"Contact\",\"resourceId\":\"2\"}]}";

        // Compute HMAC signature for the content
        string computedPayloadSignatureBase64 = ComputeContentHmacSignatureBase64(
            callbackPayloadString,
            endpointSecretKeyBase64);

        bool isValidSignature = string.Equals(
            validationSignatureBase64,
            computedPayloadSignatureBase64,
            StringComparison.Ordinal);

        Console.WriteLine("Is valid: " + isValidSignature);
    }
}
            

Synchronising missed events

Recent events can be accessed from the PlatformEvents collection endpoint:

/api/2012-02-01/auth/resources/platformevents/?skip=0&top=100&expand=PlatformEvent

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.

See PlatformEvents collection endpoint for full documentation, including supported parameters and expand options.

For a comprehensive guide covering how webhooks, PlatformEvents, and bulk synchronisation fit together, see Keeping an external system in sync.