No description
Find a file
2026-10-04 14:10:07 +02:00
.gemini update 2026-10-04 12:05:10 +02:00
fast-test.json update 2026-10-04 14:10:07 +02:00
GEMINI.md update 2026-10-04 14:10:07 +02:00
README.md update 2026-10-04 12:05:10 +02:00
test_workflow.json update 2026-10-04 14:10:07 +02:00

tags created status
projekt
fitnessstudio
automation
n8n
virtuagym
evva
airkey
2026-10-01 in-progress

Virtuagym – Automatisiertes Mitglieder-Onboarding

1. Projektbeschreibung

Für ein Fitnessstudio soll ein automatisierter Onboarding-Prozess für neue Mitglieder etabliert werden. Wenn in Virtuagym ein neuer Vertrag abgeschlossen wird, soll automatisch ein EVVA AirKey-Zugangsschlüssel erzeugt und dem neuen Mitglied zugestellt werden.

Beteiligte Systeme

System Rolle
Virtuagym Mitgliederverwaltung / Vertragssoftware
EVVA AirKey Zutrittskontrolle (digitale Smartphone-Keys)
n8n Middleware / Automatisierung (self-hosted auf OMV6)

Geplanter Ablauf

flowchart LR
    A["n8n Polling\n(alle 10 Min)"] -->|sync_from| B["Virtuagym API"]
    B -->|Neues Mitglied| C["n8n Workflow"]
    C -->|send-a-key| D["AirKey API"]
    D --> E["Mitglied erhält\ndigitalen Schlüssel"]

2. Protokoll

2026-10-01 – Projektstart & n8n Setup

Schritt 1: n8n installieren (Docker auf OMV6)

Als Testumgebung dient ein kleiner Server mit OpenMediaVault 6 (OMV6). n8n wird als Docker-Container via Docker Compose betrieben.

Voraussetzungen auf dem OMV6-Server:

  • Docker & Docker Compose installiert (OMV6 → Plugins → openmediavault-compose)
  • Netzwerkzugang auf Port 5678

Compose-Datei:

services:
  n8n:
    image: n8nio/n8n:latest
    container_name: n8n
    restart: unless-stopped
    ports:
      - "5678:5678"
    environment:
      - N8N_HOST=0.0.0.0
      - N8N_PORT=5678
      - N8N_PROTOCOL=http
      - GENERIC_TIMEZONE=Europe/Berlin
      - TZ=Europe/Berlin
      - N8N_ENCRYPTION_KEY=change-me-to-a-random-string
      # Webhook-URL anpassen wenn externer Zugriff nötig:
      # - WEBHOOK_URL=https://n8n.example.com/
    volumes:
      - n8n_data:/home/node/.n8n

volumes:
  n8n_data:

Deployment:

docker compose up -d
docker compose ps
docker compose logs -f n8n

n8n ist danach erreichbar unter: http://<server-ip>:5678

Important

Beim ersten Aufruf wird ein Owner-Account erstellt. N8N_ENCRYPTION_KEY unbedingt durch einen sicheren Zufallswert ersetzen – dieser Schlüssel verschlüsselt alle gespeicherten Credentials.

Status: ✅ n8n läuft und ist von außen erreichbar (Stand 2026-10-04)


2026-10-01 – API-Recherche

2026-10-04 – Offizielle Virtuagym API-Doku eingelesen

Schritt 2: Virtuagym API

Offizielle API-Doku: ~/repo/api-documentation-master (privates GitHub-Repo, als ZIP heruntergeladen)

Zugang & Authentifizierung
  • Base URL: https://api.virtuagym.com/api/v1/club/{club_id}
  • HTTPS ist Pflicht für alle Requests

Credentials (unter System Settings → Business Info → Advanced):

Credential Parameter-Name Beschreibung
Club-ID club_id Numerische Club-ID (in der URL)
API-Key api_key API-Schlüssel
Club Key club_secret ⚠️ Heißt in der API club_secret, nicht club_key!

Auth-Methode: Query-Parameter auf jedem Request:

GET /api/v1/club/{club_id}/member?api_key={api_key}&club_secret={club_secret}
Webhooks & Polling

Warning

Virtuagym bietet KEINE Webhooks an. Bestätigt durch offizielle Doku.

Polling-Lösung: Der sync_from-Parameter liefert nur Einträge zurück, die nach dem angegebenen Timestamp geändert wurden.

Important

Timestamps sind in Millisekunden, nicht Sekunden! (z.B. timestamp_edit: 1429522480252)

n8n Schedule Trigger (Cron/Intervall) pollt regelmäßig und speichert den letzten Timestamp via $getWorkflowStaticData().

Rate-Limit: 500 Requests/Stunde (lt. offizieller Doku)

Intervall Calls/Tag
5 Min 288
10 Min 144 ✅
15 Min 96
Endpoint: Mitglieder abrufen

GET /api/v1/club/{club_id}/member

Parameter Typ Pflicht Beschreibung
api_key string ✅ API-Schlüssel
club_secret string ✅ Club Key
sync_from int - Timestamp in Millisekunden – nur Änderungen danach
from_id int - Pagination via member_id (besser bei gleichen Timestamps)
club_member_id string - Filter nach eigener Member-ID
external_id string - Filter nach externer ID
email string - Filter nach E-Mail
rfid_tag string - Filter nach RFID-Tag
with string - memberships oder active_memberships – Verträge direkt mitladen!

Tip

with=active_memberships spart einen extra API-Call! Mitglied + aktive Verträge kommen in einer einzigen Response.

Antwort-Felder:

Feld Typ Beschreibung
member_id int Eindeutige ID
club_id int Club-Zugehörigkeit
club_member_id string Eigene Member-ID des Studios
external_id string Externe System-ID
firstname string Vorname → AirKey Person
lastname string Nachname → AirKey Person
email string E-Mail → AirKey /send-a-key/mail
phone string Telefon
mobile string Mobil → AirKey /send-a-key/sms
active boolean Aktiv/Inaktiv
member_since int Mitglied seit (Timestamp in ms)
timestamp_edit int Letzte Änderung (Timestamp in ms)
rfid_tag string RFID-Tag

Beispiel-Response (mit with=memberships):

{
  "statuscode": 200,
  "result": [
    {
      "member_id": 78166,
      "firstname": "ABC",
      "lastname": "DEF",
      "email": "abcdef@example.com",
      "active": true,
      "member_since": 1453417200000,
      "timestamp_edit": 1453471427081,
      "memberships": [
        {
          "instance_id": 2724,
          "member_id": 78166,
          "membership_id": 593,
          "active": 1,
          "cancelled": 0,
          "paused": 0,
          "stopped": 0,
          "start_date": "2016-06-09",
          "contract_start_date": "2016-07-01",
          "contract_end_date": "2016-07-31",
          "membership_name": "Premium 12M"
        }
      ]
    }
  ]
}
Endpoint: Membership Instances (Verträge)

GET /api/v1/club/{club_id}/membership/instance

Parameter Typ Beschreibung
sync_from int Timestamp in ms – nur Änderungen danach
member_id int Filter nach Mitglied
from_id int Pagination via instance_id

Antwort-Felder:

Feld Typ Beschreibung
instance_id int Vertrags-ID
member_id int Zugehöriges Mitglied
membership_id int Referenz auf Membership Definition
membership_name string Vertragsname
active boolean Vertrag aktiv?
cancelled boolean Manuell gekündigt?
paused boolean Pausiert?
stopped boolean Sofort beendet?
completed boolean Regulär ausgelaufen?
contract_autorenewed boolean Bereits verlängert?
start_date string Startdatum (YYYY-MM-DD)
contract_start_date string Vertragsbeginn (YYYY-MM-DD)
contract_end_date string Vertragsende (YYYY-MM-DD)
Endpoint: Membership Definitions (Produkt-Katalog)

GET /api/v1/club/{club_id}/membership/definition

Liefert den Produkt-Katalog mit Preisen, Laufzeiten und Zutrittszeiten:

Feld Beschreibung
membership_id Produkt-ID
membership_name Name (z.B. "Premium 12M")
membership_duration Laufzeit (z.B. 12)
membership_duration_type weeks / months
membership_auto_renew Automatische Verlängerung?
membership_price Preis
membership_price_term monthly / weekly / four_weekly / total
access_times Array mit Zutrittszeiten pro Wochentag!

Tip

Die access_times aus der Membership Definition könnten direkt als AirKey-Zeitfenster übernommen werden!

Beispiel access_times:

"access_times": [
  { "day": "Monday", "start_time": "06:00:00", "end_time": "22:00:00" },
  { "day": "Tuesday", "start_time": "06:00:00", "end_time": "22:00:00" }
]
HTTP Status Codes
Code Bedeutung
200 OK
201 Created (z.B. neues Mitglied)
401 Ungültige/fehlende Credentials
404 Resource nicht gefunden
420 Business-Logic-Fehler (z.B. Member nicht gefunden)
423 Concurrent Update (Member wird gerade aktualisiert)
Kosten & Voraussetzungen
  • €10/Monat pro Standort als API-Add-on
  • Paket „Manage" oder „Manage & Grow" erforderlich
  • Module Clients & Staff + Membership & Billing müssen aktiv sein

Schritt 3: EVVA API (AirKey bestätigt ✅)

AirKey vs. Xesar
Kriterium AirKey Xesar
Architektur Cloud-native SaaS On-Premise Server
API-Typ REST (JSON/HTTPS) MQTT + lokale REST
Authentifizierung API Key (X-API-Key Header) + IP-Whitelist Mutual TLS (mTLS)
Remote Key-Versand ✅ SMS & E-Mail Endpoints Eingeschränkt (SMS via XMS)
Doku Öffentliches Swagger UI Nur über Partner-Portal
Authentifizierung
  • HTTP Header: X-API-Key: <key>
  • Bis zu 10 API-Keys im AirKey-Portal unter Settings → Cloud Interface
  • Optional: IP-Whitelisting
  • Alle Kommunikation über HTTPS/TLS 1.2+
Der "Send a Key" Endpoint (Kernstück für unser Szenario!)

AirKey bietet All-in-One-Endpoints, die in einem einzigen Call:

  1. Person anlegen
  2. Smartphone-Medium erstellen
  3. Zugangsrechte zuweisen
  4. Onboarding-Link per SMS oder E-Mail versenden
Endpoint Beschreibung
POST /cloud/v1/send-a-key/sms Schlüssel per SMS versenden
POST /cloud/v1/send-a-key/mail Schlüssel per E-Mail versenden

Ablauf beim Mitglied:

  1. SMS/E-Mail mit Onboarding-Link erhalten
  2. EVVA AirKey App installieren (iOS/Android)
  3. Registrierungscode aktivieren
  4. Smartphone generiert kryptographisches Schlüsselpaar
  5. Zukünftige Berechtigungsänderungen kommen per Push-Notification
Weitere relevante Endpoints
Endpoint Beschreibung
GET/POST /v1/persons Personen verwalten
GET /v1/media/phones Smartphone-Medien auflisten
POST /v1/authorizations/simple Zeitgebundene Zugangsrechte
POST /v1/authorizations/advanced Komplexe Zeitpläne (Wochentage etc.)
DELETE /v1/authorizations Zugang sofort entziehen
GET /v1/locks Schließzylinder auflisten
GET /v1/areas Logische Bereiche (Türgruppen)
GET /v1/lock-protocol-limit Audit-Log (wer hat wann wo geöffnet)
Dokumentation & Test-Umgebung
Kosten
  • Aktivierung Cloud Interface: einmalig 350 KeyCredits
  • Pro Schlüsseländerung: 1 KeyCredit (lesen/löschen ist kostenlos)
  • KeyCredit Unlimited: Flatrate für 12 oder 36 Monate (empfohlen bei hohem Durchsatz)
  • Sandbox/Test: komplett kostenlos

3. Integrationsarchitektur

Geplanter n8n-Workflow

flowchart TD
    A["⏰ n8n Schedule Trigger\n(alle 10 Min)"] --> B["HTTP Request: GET\nVirtuagym /member\n?sync_from=...&with=active_memberships"]
    B --> C{"Neues Mitglied\nmit aktivem Vertrag?"}
    C -->|Nein| D["Ende"]
    C -->|Ja| E["Mitgliederdaten\nextrahieren\n(Name, Mobile/Email,\nVertragslaufzeit)"]
    E --> F["HTTP Request: POST\nAirKey /send-a-key/sms\noder /send-a-key/mail"]
    F --> G["Mitglied erhält\ndigitalen Schlüssel"]
    G --> H["Static Data:\nsync_from aktualisieren"]

Ablauf im Detail

  1. Trigger: n8n Schedule Trigger alle 10 Minuten
  2. Polling: GET /member?sync_from={lastSync}&with=active_memberships – ein einziger Call liefert Mitglied + Vertragsdaten
  3. Filter: Nur Mitglieder bei denen timestamp_edit > member_since (annähernd gleich = neu erstellt) UND mindestens eine active Membership
  4. Mapping: Virtuagym-Daten → AirKey-Format:
    • firstname + lastname → AirKey Person
    • mobile oder email → Versandkanal
    • contract_end_date → AirKey validTo
    • access_times (aus Membership Definition) → AirKey Zeitfenster
  5. Key-Versand: POST /cloud/v1/send-a-key/sms oder /mail
  6. State Update: sync_from auf aktuellen Timestamp setzen via $getWorkflowStaticData('node')

4. Offene Aufgaben

Sofort

  • n8n auf OMV6 deployen ✅
  • n8n von außen erreichbar ✅
  • Virtuagym API-Zugang beantragt und erhalten ✅
  • Offizielle API-Doku eingelesen ✅
  • EVVA AirKey Cloud Interface aktivieren (350 KeyCredits)
  • AirKey Sandbox testen (https://integration.api.airkey.evva.com/docs/)

Klärungsbedarf

  • Welches EVVA-System ist verbaut? → AirKey bestätigt ✅
  • Schlüsselversand per SMS oder E-Mail? (oder beides?)
  • Welche Türen/Bereiche sollen Neumitglieder erhalten?
  • Gültigkeitszeitraum der Schlüssel = Vertragslaufzeit?
  • Sollen access_times aus der Membership Definition als AirKey-Zeitfenster übernommen werden?

Später

  • n8n-Workflow bauen und testen
  • Fehlerbehandlung (z.B. ungültige Telefonnummer, AirKey-Fehler)
  • Vertragskündigung → automatischer Schlüsselentzug?
  • End-to-End-Test mit echtem Mitglied