Events

Everything that happens in an Akiles organization is logged as an event: someone opening a door, a member being created or edited, a gadget changing state… Events are the audit trail shown in the admin panel’s log, and the way your application can react to what happens in the organization.

Events are structured in “subject - verb - object” form:

For example:

The full event object is documented in the API reference.

Timestamps

Events have two timestamps:

They differ for events that reach the server late. The typical case is offline devices: gadget action events originate from the Akiles device when the user uses PINs, NFC cards or phone to open. If the device is offline (i.e. has no connection to Akiles Cloud), it buffers events in local persistent memory and reports them later when it becomes online again. Such events get created_at set to the moment they were reported, and occurred_at set to the moment they originally happened.

Receiving events

There are two ways to receive events, all delivering the same event objects:

Polling

The “list events” endpoint returns events in reverse chronological order, with cursor pagination. To poll for new events:

  1. Remember the ID of the newest event you’ve processed.
  2. Periodically fetch the newest events, following cursor_next until you reach the remembered event ID (or run out of events).
  3. Process the new events (oldest first, if order matters to you), and update the remembered ID.

The endpoint supports filters (object type, verb, member, gadget, time ranges…) to narrow what you fetch; they’re documented in the reference.

Polling is a good fit for batch-style consumers (e.g. importing events into your own database every night for a billing batch job). If you need lower latency, use webhooks instead.

Webhooks

Webhooks allow your server to receive real-time notifications about events happening in the Akiles organization.

Webhooks send a POST request to the indicated URL when a matching event occurs. The request body is a JSON-encoded event, same as you would get in the response of the “get event” endpoint.

URL can be HTTP or HTTPS. We strongly recommend using HTTPS for production.

You manage webhooks with the webhook endpoints. For debugging, you can view the webhook delivery logs for your application in the Developer Center.

Webhook objects are scoped to your OAuth application. Webhooks you create won’t be visible to other OAuth applications installed in the same org and vice versa. All your application’s webhooks in an organization will be deleted when your app gets de-authorized.

Even if you have read-only access, you can still create/edit/delete webhooks. This allows you to receive real-time updates in an application that only needs to read data from the Akiles organization.

Filtering

Each webhook has a “filter” which specifies which events should be sent to it. A filter is a list of “filter rules”.

Each rule is an object mapping filter names to the value to match. The supported filters are exactly the ones of the “list events” endpoint, with the same names, value formats and operator suffixes (e.g. created_at:gt). All values are strings, formatted exactly as you’d pass them in the list query parameter.

A rule matches an event if all its filters match. object.type must always be specified, i.e. it’s NOT possible to subscribe to all object types. To match all verbs of an object type, simply don’t filter on verb.

The webhook filter matches if any of the rules in the array match.

Some examples:

An event will be sent to this webhook if it matches at least one rule. If it matches multiple rules, it will only be sent once. If it matches multiple webhook objects, it will be sent once per webhook to its respective URL, even if the URL is the same.

Filters are validated when creating or editing the webhook: unknown filter names or invalid values fail with 400 Bad Request.

Expansion

Each webhook has an “expand” field, a list of expands applied to the event when delivering it. The delivered event embeds the expanded objects, the same way the response of the “get event” endpoint does when called with the expand query parameter. This saves you the extra API requests to fetch the objects the event references.

The supported expands are the ones of the event object, including nested expands with dots. For example, "expand": ["object_member", "object_member.group_associations"] delivers events with the full member object embedded under object_member, including its group associations.

Expands are validated when creating or editing the webhook: requesting an expand the event doesn’t support fails with 400 Bad Request.

Signature

If an attacker learns the URL of your webhook, they could send requests themselves to it, spoofing webhooks. To prevent this, Akiles signs the webhook requests so that your server can check they come from Akiles and not an attacker. It’s strongly recommended to verify the signature.

The signature is the result of applying HMAC-SHA256 to the raw request body bytes, using the webhook secret as a key. It’s sent in the X-Akiles-Sig-SHA256 HTTP header.

The secret is returned when you create the webhook. It’s not possible to view the secret of an existing webhook, you have to delete and recreate it instead.

The following is an example of how to receive webhooks and validate the signature using nodejs:

import url from 'url'
import http from 'http'
import crypto from 'crypto'

// replace with the secret you get when creating the webhook.
const webhookSecret = 'f305d484fd10de285b00bb203659e863';

const app = http.createServer((request, response) => {
    let body = [];
    request.on('data', (chunk) => {
        body.push(chunk);
    }).on('end', () => {
        body = Buffer.concat(body).toString();

        const gotSig = Buffer.from(request.headers['x-akiles-sig-sha256'], 'hex');
        const expectedSig = crypto.createHmac("sha256", webhookSecret).update(body).digest();
        const ok = crypto.timingSafeEqual(gotSig, expectedSig);
        if (!ok) {
            response.writeHead(400);
            response.write('bad sig');
            response.end();
            return;
        }

        const event = JSON.parse(body.toString());
        console.log('Got event!');
        console.log(event);

        response.writeHead(200);
        response.write('cool');
        response.end();
    });
});

app.listen(8088);

Delivery

Webhook delivery is considered successful if your server returns a 2xx status code.

Unsuccessful deliveries are retried, with an exponential backoff starting at a few seconds and doubling every time, for up to 1 hour.