Data Events

Note

SOS Data Events for Audit-Trail is only available for customers with Enterprise support plan and are affected by a specific extra cost.

SOS Data Events for Audit-Trail gives you detailed logs for all API requests executed against the SOS service, including accepted and rejected requests, information about the querier and the affected objects.

Events are sent in batches of up to 1'000 events to a webhook you provide. Calls to the webhook are signed with a HMAC-SHA256 signature within the exo-audittrail-signature header with a key provided by Exoscale during the configuration process.

Guarantees

SOS Data Events for Audit-Trail are delivered on a best-effort basis, the exhaustivity and delivery duration of events is not guaranteed. The data event can be used to understand the traffic against your bucket. Some events can be lost, duplicated or significantly delayed.

Requests against the wrong region, resulting in a redirection, do not generate any data-events and will not be visible. When the request is executed against the right zone after following the redirection, data events will be generated.

Configuration

The configuration is per-bucket and will include all data events. A webhook url must be provided, it must be a HTTPS server exposed on the port 443 and handle POST operations.

This webhook url must be provided to the exoscale support. In return you will be provided with a symmetric key used for signing the body of requests and validating its origin.

Sample output

This formatted json represents the type of data which is included by the SOS Data Events for Audit-Trail. All of the information about the source of the request, the request itself and the actions are included.

[
  {
    "request-id": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
    "user-agent": "Mozilla/5.0 ...",
    "zone": "at-vie-1",
    "status": 200,
    "resource": "my-bucket",
    "source-ip": "1.2.3.4",
    "uri": "https://my-bucket.sos-at-vie-1.exo.io",
    "timestamp": "2026-01-01T01:01:01.123456789Z",
    "handler": "show-object-cors",
    "request-method": "OPTIONS",
    "api-key": null
  },
  {
    "iam-user": {
      "id": "cccccccc-cccc-cccc-cccc-cccccccccccc",
      "role": {
        "id": "dddddddd-dddd-dddd-dddd-dddddddddddd"
      }
    },
    "request-id": "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb",
    "user-agent": "Mozilla/5.0 ...",
    "iam-role": {
      "id": "dddddddd-dddd-dddd-dddd-dddddddddddd",
      "name": "Owner"
    },
    "zone": "at-vie-1",
    "status": 200,
    "resource": "my-bucket",
    "source-ip": "4.3.2.1",
    "uri": "https://my-bucket.sos-at-vie-1.exo.io/my-object-name.txt",
    "timestamp": "2026-01-01T01:01:01.123456789Z",
    "handler": "delete-object",
    "request-method": "DELETE",
    "api-key": null
  }
]

Webhook example

As a starting point, here is an example of a webhook which can be used to receive, validate and process data events.

import json
import base64
import hashlib
import hmac
from http.server import BaseHTTPRequestHandler

....

class SimpleHTTPRequestHandler(BaseHTTPRequestHandler):
    signing_key = None

    ....

    def do_POST(self):
        content_length = int(self.headers.Get("Content-Length", 0))
        body = self.rfile.read(content_length)

        # Verify HMAC-SHA256 signature
        signature_header = self.headers.get("exo-audittrail-signature")
        if not signature_header:
            self.send_response(400)
            self.end_headers()
            self.wfile.write(b"Bad Request: Missing signature header")
            return

        try:
            # Decode the signing key from base64
            key = base64.b64decode(self.signing_key)
            # Compute expected signature
            expected_signature = hmac.new(key, body, hashlib.sha256).hexdigest()

            if not hmac.compare_digest(signature_header, expected_signature):
                self.send_response(400)
                self.end_headers()
                self.wfile.write(b"Bad Request: Invalid signature")
                return
        except Exception as e:
            self.send_response(400)
            self.end_headers()
            self.wfile.write(b"Bad Request: Signature verification error")
            return

        data = json.loads(body.decode("utf-8"))

        ....

        self.send_response(200)
        self.end_headers()
Last updated on