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:
- Subject: who did the action. It can be a member, an administrator, an OAuth application…
- Verb: what action was done on the object. Create, edit, delete, use…
- Object: what object the action was done on. A member, a gadget…
For example:
- Gadget action events (i.e. someone opening a door) are reported with the member in the subject,
useas verb, and the gadget action as object. - Member editions are reported with the admin user who did the action in the subject,
editas the verb, and the member as the object.
The full event object is documented in the API reference.
Timestamps
Events have two timestamps:
created_at: the time the event was recorded by the server. Events are ordered by it.occurred_at: the time the event actually happened. For most events it equalscreated_at.
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: periodically fetch new events from the list endpoint. Simplest, no extra infrastructure, but latency is your polling interval.
- Webhooks: Akiles POSTs each event to your HTTPS endpoint. Near real time, with automatic retries; requires a publicly reachable server.
Polling
The “list events” endpoint returns events in reverse chronological order, with cursor pagination. To poll for new events:
- Remember the ID of the newest event you’ve processed.
- Periodically fetch the newest events, following
cursor_nextuntil you reach the remembered event ID (or run out of events). - 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:
"filter": []: Matches no events. Events match if at least one filter rule matches, which can’t happen if there’s no rules at all."filter": [{"verb": "use", "object.type": "gadget_action"}]: Matches gadget action events."filter": [{"object.type": "member"}]: Matches all events related to members (creation, edition, deletion…)"filter": [{"verb": "edit", "object.type": "member"}]: Matches member edition events."filter": [{"verb": "edit", "object.type": "member"}, {"verb": "use", "object.type": "gadget_action"}]: Matches member editions, and gadget actions."filter": [{"verb": "use", "object.type": "gadget_action", "object.gadget_id": "gad_3merw5pijgst6g8mrhgc"}]: Matches actions of one specific gadget."filter": [{"verb": "use", "object.type": "gadget_action", "subject.member_id": "mem_3merw5pijgst6g8mrhgc"}]: Matches actions performed by one specific member.
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.