Webhooks

Bikeep can send HTTP webhook notifications to your server when device events occur. Webhooks are useful for monitoring device health, alerting on alarms and anomalous device states, tracking connectivity, and receiving reports.

Setup

Webhook endpoints are configured by Bikeep. To register your endpoint:

  1. Email info@bikeep.com with your endpoint URL
  2. Bikeep will configure the webhook and provide you with a seed for hash verification
  3. Specify which event types you want to receive (see table below)

Tip: You can request alarm filtering by category or reason — for example, receive only security alarms, or only wire_cut events. Specify your filter preferences during setup.


Webhook Types

Type Tier When It Fires Delivery Key Fields
connection Public Device goes online or offline Aggregated (5 min) connection
connection_confirmed Public Location offline for 2+ hours — confirms a real outage, not a brief flap Aggregated (15 min) connection
alarm Public Hardware alarm triggered Aggregated (1 min) alarm_reason, alarm_category
parking_reminder Public Parking duration reminder Aggregated (1 day) parking_details[]
status Partner Anomalous device state persisted (e.g., door left open 5+ min, interim state timed out) Aggregated (5 min) status
report Partner Report completed Direct report_type, report_url, url_valid_until
maintenance_log Partner Maintenance log entry created, updated, or deleted Direct action, log_entry_id, description, targets

Availability Tiers

  • Public types are available to all integrators with API access.
  • Partner types require partner-level access. Contact Bikeep if you need these event types.

Payload Format

All webhooks are sent as POST requests with a JSON body. The version 2 envelope includes a top-level type field identifying the event type:

{
  "type": "connection",
  "data": { ... },
  "version": 2,
  "hash": "4161215c299681565b4309c7c60dddce0b25b86d..."
}
Field Description
type Event type identifier (e.g., "connection", "alarm", "status")
data Event payload — structure varies by type (see examples below)
version Payload format version (currently 2)
hash SHA-256 hash for verifying payload authenticity (see Hash Verification)

Connection payload

Sent when a device goes online or offline.

{
  "type": "connection",
  "data": {
    "type": "connection_changed",
    "location_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "location_name": "#1234 Office Building A",
    "location_address": "123 Main Street",
    "location_code": "#1234",
    "location_coordinates": {
      "latitude": 59.42776,
      "longitude": 24.81521
    },
    "location_status": "LAUNCHED",
    "connection": "offline",
    "timestamp": "2025-12-15T10:30:00Z",
    "devices": [
      {
        "device_id": "f1e2d3c4-b5a6-7890-abcd-ef1234567890",
        "device_nr": "1"
      }
    ]
  },
  "hash": "4161215c299681565b4309c7c60dddce0b25b86d36ce72a1e43b58d8f7be88cf",
  "version": 2
}

Note: The data.type field contains "connection_changed" for backwards compatibility with version 1 consumers. The top-level type field contains the canonical name "connection".

Connection Confirmed payload

Sent when a location has been offline for 2+ hours, confirming a real outage rather than a brief connectivity flap. This webhook fires only for offline events — it does not notify when a location comes back online. Use the connection webhook or poll the location endpoint for online-recovery detection.

{
  "type": "connection_confirmed",
  "data": {
    "location_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "location_name": "#1234 Office Building A",
    "location_address": "123 Main Street",
    "location_code": "#1234",
    "location_coordinates": {
      "latitude": 59.42776,
      "longitude": 24.81521
    },
    "location_status": "LAUNCHED",
    "connection": "offline",
    "timestamp": "2025-12-15T12:30:00Z",
    "devices": [
      {
        "device_id": "f1e2d3c4-b5a6-7890-abcd-ef1234567890",
        "device_nr": "1"
      }
    ]
  },
  "hash": "b2c3d4e5f67890abcdef1234567890abcdef1234567890abcdef1234567890ab",
  "version": 2
}

Alarm payload

Sent when a hardware alarm is triggered (e.g., case open, wire cut, low voltage).

{
  "type": "alarm",
  "data": {
    "type": "alarm",
    "location_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "location_name": "#1234 Office Building A",
    "location_address": "123 Main Street",
    "location_code": "#1234",
    "location_coordinates": {
      "latitude": 59.42776,
      "longitude": 24.81521
    },
    "location_status": "LAUNCHED",
    "alarm_reason": "case_open",
    "alarm_category": "security",
    "timestamp": "2025-12-15T10:35:00Z",
    "devices": [
      {
        "device_id": "f1e2d3c4-b5a6-7890-abcd-ef1234567890",
        "device_nr": "1"
      }
    ]
  },
  "hash": "a1b2c3d4e5f67890abcdef1234567890abcdef1234567890abcdef1234567890",
  "version": 2
}

Note: The data.type field contains "alarm" for backwards compatibility with version 1 consumers.

Alarm categories and reasons

Category Reasons
security wire_cut, case_open, lock_physically_opened, tampered
technical ups_power_lost, ground_fault, low_voltage, not_responding, camera_not_responding

Parking Reminder payload

Sent as a daily reminder listing devices with active parking sessions exceeding a configured duration threshold.

{
  "type": "parking_reminder",
  "data": {
    "timestamp": "2025-12-16T08:00:00Z",
    "location_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "location_name": "#1234 Office Building A",
    "location_address": "123 Main Street",
    "location_code": "#1234",
    "location_coordinates": {
      "latitude": 59.42776,
      "longitude": 24.81521
    },
    "location_status": "LAUNCHED",
    "parking_details": [
      {
        "user_id": "c3d4e5f6-a1b2-7890-abcd-ef1234567890",
        "device_id": "f1e2d3c4-b5a6-7890-abcd-ef1234567890",
        "device_alias": "#1234-01",
        "duration": 172800
      },
      {
        "user_id": "d4e5f6a1-b2c3-7890-abcd-ef1234567890",
        "device_id": "a9b8c7d6-e5f4-3210-abcd-ef1234567890",
        "device_alias": "#1234-02",
        "duration": 86400
      }
    ]
  },
  "hash": "c3d4e5f6a1b27890abcdef1234567890abcdef1234567890abcdef1234567890",
  "version": 2
}
Field Description
parking_details[].user_id ID of the user with an active parking session
parking_details[].device_id Device where the item is parked
parking_details[].device_alias Human-readable device identifier
parking_details[].duration Parking duration in seconds

Status payload

Sent when a device is in an anomalous state that has persisted past its expected duration — for example, a door (bike house door or locker door) left open for more than 5 minutes, or an interim state (ALLOCATED, LOCKING, UNLOCKING) that did not resolve within its timeout. This is an alarming webhook for detecting stuck or abnormal devices, not a general event stream for device state changes. Requires partner-level access.

{
  "type": "status",
  "data": {
    "location_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "location_name": "#1234 Office Building A",
    "location_address": "123 Main Street",
    "location_code": "#1234",
    "location_coordinates": {
      "latitude": 59.42776,
      "longitude": 24.81521
    },
    "location_status": "LAUNCHED",
    "status": "UNLOCKING",
    "timestamp": "2025-12-15T10:32:00Z",
    "devices": [
      {
        "device_id": "f1e2d3c4-b5a6-7890-abcd-ef1234567890",
        "device_nr": "1"
      }
    ]
  },
  "hash": "d4e5f6a1b2c37890abcdef1234567890abcdef1234567890abcdef1234567890",
  "version": 2
}

The status field contains the device’s current (anomalous) state. In practice you will see interim states that failed to resolve: ALLOCATED, LOCKING, UNLOCKING. See State Machines for what each state means and its expected duration.

Note: The location_status field in the webhook payload (e.g., LAUNCHED, MAINTENANCE) describes the location’s operational status, not the device state. Do not confuse location_status with the status field.

Maintenance Log payload

Sent when a maintenance log entry is created, updated, or deleted. Requires partner-level access.

{
  "type": "maintenance_log",
  "data": {
    "timestamp": "2025-12-15T10:00:00Z",
    "organization_id": "e5f6a1b2-c3d4-7890-abcd-ef1234567890",
    "location_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "location_name": "#1234 Office Building A",
    "location_code": "#1234",
    "location_status": "LAUNCHED",
    "action": "created",
    "log_entry_id": "d4e5f6a1-b2c3-7890-abcd-ef1234567890",
    "description": "Replaced locking mechanism on device #3",
    "targets": ["f1e2d3c4-b5a6-7890-abcd-ef1234567890"],
    "priority": "high",
    "status": "open",
    "status_changed_at": "2025-12-15T10:00:00Z",
    "seconds_spent": 3600,
    "resolution": "Replaced faulty component",
    "cost": "150.00"
  },
  "hash": "f6a1b2c3d4e57890abcdef1234567890abcdef1234567890abcdef1234567890",
  "version": 2
}
Field Description
location_id UUID of the associated location
location_name Human-readable location name
location_code Short location code (e.g., #1234)
location_status Location operational status (e.g., LAUNCHED, MAINTENANCE)
action The action performed: created, updated, or deleted
log_entry_id UUID of the maintenance log entry
description Human-readable description of the maintenance task
targets List of affected device or location UUIDs
priority Priority level of the maintenance entry
status Maintenance entry status (e.g., open, resolved)
seconds_spent Time spent on the maintenance task in seconds
resolution Resolution description (if resolved)
cost Cost of the maintenance task

Report payload

Sent when a report has been generated and is ready for download. Requires partner-level access. This event has a different structure — it contains organization and report metadata instead of location fields.

{
  "type": "report",
  "data": {
    "timestamp": "2025-12-15T11:00:00Z",
    "organization_id": "e5f6a1b2-c3d4-7890-abcd-ef1234567890",
    "report_id": "rpt-1234-5678-abcd",
    "report_type": "usage_summary",
    "report_description": "Monthly usage summary for December 2025",
    "report_url": "https://reports.bikeep.com/download/rpt-1234-5678-abcd",
    "url_valid_until": "2025-12-22T11:00:00Z"
  },
  "hash": "e5f6a1b2c3d47890abcdef1234567890abcdef1234567890abcdef1234567890",
  "version": 2
}
Field Description
report_url Temporary download URL for the report file
url_valid_until Expiration time of the download URL (UTC, ISO 8601)

Versioning

The current webhook payload version is 2. Changes from version 1:

  • A top-level type field was added to the envelope, identifying the event type (e.g., "connection", "alarm", "status")
  • New event types were added: connection_confirmed, parking_reminder, status, report
  • New fields were added to existing events: location_status, alarm_category

For backwards compatibility, the connection and alarm events retain a data.type field with their version 1 names ("connection_changed" and "alarm" respectively). Newer event types do not include data.type.

The hash verification algorithm is unchanged between versions — it still uses SHA256(JSON(data) + seed).


Hash Verification

Every webhook includes a hash field for verifying the payload’s authenticity. The hash is computed as:

SHA256( JSON(data) + seed )

Where:

  • JSON(data) is the JSON-serialized data object from the payload
  • seed is the pre-shared secret provided by Bikeep during webhook setup
  • The + is string concatenation

Verification example (Node.js)

const crypto = require('crypto');

function verifyWebhook(payload, seed) {
  const dataJson = JSON.stringify(payload.data);
  const expectedHash = crypto
    .createHash('sha256')
    .update(dataJson + seed)
    .digest('hex');
  return expectedHash === payload.hash;
}

Verification example (Python)

import hashlib
import json

def verify_webhook(payload, seed):
    data_json = json.dumps(payload['data'], separators=(',', ':'))
    expected = hashlib.sha256((data_json + seed).encode()).hexdigest()
    return expected == payload['hash']

Response Requirements

Your endpoint must:

  • Respond with a 200 status code to acknowledge receipt
  • Respond within 5 seconds (requests time out after 5 seconds per attempt)

Non-200 responses with status code 5xx trigger retries (see below). Client errors (4xx) are not retried.


Delivery Characteristics

Delivery modes

Mode Behavior Availability
Aggregated Events are batched and delivered at regular intervals to prevent flooding connection, connection_confirmed, alarm, parking_reminder, status
Direct Events are delivered immediately as they occur report, maintenance_log

Aggregation intervals

Event Type Default Interval
alarm 1 minute
connection 5 minutes
connection_confirmed 15 minutes
status 5 minutes
parking_reminder 1 day
report Immediate (always direct)
maintenance_log Immediate (always direct)

Why aggregation? IoT devices can fire many events rapidly (e.g., connectivity flapping, rapid state changes). Aggregation batches these into regular intervals to avoid overwhelming your endpoint.

Retry policy

  • Retries: Up to 3 retries on 5xx server errors and network/connection errors
  • Backoff: 100 ms between retries
  • Not retried: 4xx client errors
  • Ordering: Not guaranteed — use the timestamp field for ordering