Skip to main content
Grantex provides real-time event streaming endpoints that push authorization lifecycle events as they happen. Combined with the @grantex/destinations package, you can forward events to SIEMs, data warehouses, and message brokers without writing custom plumbing.

Architecture

┌─────────────────┐     SSE / WebSocket     ┌──────────────────┐
│  Grantex Auth    │ ─────────────────────► │  EventSource      │
│  Service         │                         │  (@grantex/       │
│                  │                         │   destinations)   │
└─────────────────┘                         └────────┬─────────┘

                                          ┌──────────┼──────────┐
                                          │          │          │
                                          ▼          ▼          ▼
                                       Datadog    Splunk      S3
                                       Splunk     BigQuery    Kafka
The EventSource class connects to the SSE stream and dispatches each event to one or more destinations you configure. Destinations buffer events and flush them in batches for efficiency.

Event Types

EventWhen it fires
grant.createdA new grant is issued after the user completes the consent flow
grant.revokedA grant is revoked (root or cascade revocation)
token.issuedA grant token is issued (initial exchange or refresh)
budget.thresholdA budget usage threshold is crossed (e.g., 80%)
budget.exhaustedA budget is fully consumed
Each event has the same envelope:
{
  "id": "evt_01JXYZ...",
  "type": "grant.created",
  "createdAt": "2026-03-01T12:00:00Z",
  "data": {
    "grantId": "grnt_01...",
    "agentId": "ag_01...",
    "principalId": "user-123",
    "scopes": ["calendar:read"]
  }
}

SSE Endpoint

GET /v1/events/stream returns a Server-Sent Events stream. Authenticate with your API key as a Bearer token.

curl

curl -N -H "Authorization: Bearer $GRANTEX_API_KEY" \
  "https://api.grantex.dev/v1/events/stream"
Filter by event type with the types query parameter (comma-separated):
curl -N -H "Authorization: Bearer $GRANTEX_API_KEY" \
  "https://api.grantex.dev/v1/events/stream?types=grant.created,grant.revoked"

TypeScript

const res = await fetch('https://api.grantex.dev/v1/events/stream', {
  headers: { Authorization: `Bearer ${process.env.GRANTEX_API_KEY}` },
});

const reader = res.body!.getReader();
const decoder = new TextDecoder();

while (true) {
  const { done, value } = await reader.read();
  if (done) break;

  const text = decoder.decode(value, { stream: true });
  for (const line of text.split('\n')) {
    if (line.startsWith('data: ')) {
      const event = JSON.parse(line.slice(6));
      console.log(event.type, event.data);
    }
  }
}

Python

import httpx
import json
import os

with httpx.stream(
    "GET",
    "https://api.grantex.dev/v1/events/stream",
    headers={"Authorization": f"Bearer {os.environ['GRANTEX_API_KEY']}"},
) as response:
    for line in response.iter_lines():
        if line.startswith("data: "):
            event = json.loads(line[6:])
            print(event["type"], event["data"])

WebSocket Endpoint

GET /v1/events/ws upgrades to a WebSocket connection. Each message is a JSON-encoded event.

TypeScript

const ws = new WebSocket('wss://api.grantex.dev/v1/events/ws', {
  headers: { Authorization: `Bearer ${process.env.GRANTEX_API_KEY}` },
});

ws.on('message', (raw) => {
  const event = JSON.parse(raw.toString());
  console.log(event.type, event.data);
});

Python

import asyncio
import json
import os
import websockets

async def listen():
    uri = "wss://api.grantex.dev/v1/events/ws"
    headers = {"Authorization": f"Bearer {os.environ['GRANTEX_API_KEY']}"}

    async with websockets.connect(uri, additional_headers=headers) as ws:
        async for message in ws:
            event = json.loads(message)
            print(event["type"], event["data"])

asyncio.run(listen())

@grantex/destinations Package

The @grantex/destinations package provides a high-level EventSource class that connects to the SSE stream and dispatches events to one or more destinations. Install it with:
npm install @grantex/destinations

Basic Usage

import { EventSource, DatadogDestination, SplunkDestination } from '@grantex/destinations';

const source = new EventSource({
  url: 'https://api.grantex.dev',
  apiKey: process.env.GRANTEX_API_KEY!,
  types: ['grant.created', 'grant.revoked', 'token.issued'],
});

// Add one or more destinations
source.addDestination(new DatadogDestination({
  apiKey: process.env.DD_API_KEY!,
}));

source.addDestination(new SplunkDestination({
  hecUrl: 'https://splunk.example.com:8088',
  hecToken: process.env.SPLUNK_HEC_TOKEN!,
}));

// Start streaming — runs until you call stop()
await source.start();

Available Destinations

DestinationClassTarget
DatadogDatadogDestinationDatadog Logs API
SplunkSplunkDestinationSplunk HTTP Event Collector
Amazon S3S3DestinationS3 bucket (NDJSON files)
BigQueryBigQueryDestinationGoogle BigQuery table
KafkaKafkaDestinationApache Kafka topic

Shared Configuration

All destinations accept these base options:
OptionTypeDefaultDescription
batchSizenumberVariesNumber of events to buffer before flushing
flushIntervalMsnumberFlush on a timer (milliseconds) even if batch is not full

Graceful Shutdown

Always call stop() to flush pending events and close connections:
process.on('SIGTERM', async () => {
  await source.stop();
  process.exit(0);
});

Custom Destinations

Implement the EventDestination interface to build your own:
import type { EventDestination, GrantexEvent } from '@grantex/destinations';

class MyDestination implements EventDestination {
  readonly name = 'my-destination';

  async send(events: GrantexEvent[]): Promise<void> {
    for (const event of events) {
      // Forward to your system
    }
  }

  async flush(): Promise<void> {
    // Flush any buffered events
  }

  async close(): Promise<void> {
    // Clean up resources
  }
}

Next Steps