# Hartslag Integration Guide (Heartbeat API)

> Note: This guide focuses on **safe, low-impact** event tracking. Heartbeats should never block user requests or break your app.

## Overview

Hartslag is a lightweight event tracking service. You send timestamped **heartbeats** whenever something meaningful happens (user activity, background jobs, business events), and Hartslag aggregates counts over time.

### What is a “heartbeat”?

A heartbeat is a single, timestamped occurrence of an event:

- **Real-time activity monitoring**: Track actions and system events as they happen
- **Aggregate reporting**: View event counts aggregated by time periods (today, week, month, total)
- **Flexible event tracking**: Use any event name (recommended: kebab-case)
- **Weighted metrics**: Use `weight` to track values like durations, file sizes, or amounts

### How It Works

1. Your app sends a `POST` request to the API with an `event` name (and optional metadata)
2. Hartslag records the heartbeat
3. You view aggregated metrics in the dashboard
4. You can export historical data as CSV

**Base URL**: `http://hartslag.no`

> The base URL is the URL of **your Hartslag instance**. When viewing the hosted docs, the site will typically show the correct base URL for that environment.

## Quick Start

> Important: Send heartbeats **asynchronously** (queue/background) whenever possible. Tracking must never slow down user requests.

### 1. Get Your API Token

1. Log in to Hartslag
2. Create or select an Application
3. Copy the API token (bearer token)

### 2. Send Your First Heartbeat

```bash
BASE_URL="http://hartslag.no"
TOKEN="YOUR_TOKEN_HERE"

curl -X POST "$BASE_URL/api/v1/heartbeat" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "event": "user-login",
    "weight": 1,
    "user_id": 123,
    "created_at": "2024-01-17T10:30:00Z"
  }'
```

**Success**: `201 Created`

Response body (current): empty JSON (e.g. `[]`).

## API Reference

### POST /api/v1/heartbeat

Records a single heartbeat.

**Headers**

- `Authorization: Bearer {token}` (required)
- `Content-Type: application/json` (required)

**Body parameters**

| Parameter | Type | Required | Description |
|---|---:|:---:|---|
| `event` | string | Yes | Event name. Recommended: kebab-case (e.g. `user-login`). |
| `weight` | integer | No | Default: `1`. Use for weighted metrics (durations, amounts, etc.). |
| `user_id` | integer | No | Optional user identifier for per-user activity. |
| `created_at` | string/date | No | Optional timestamp. The API accepts Laravel “date” formats. Recommended: ISO 8601 / RFC 3339 (e.g. `2024-01-17T10:30:00Z`). |

**Success response**

- Status: `201 Created`
- Body: empty JSON (current)

**Error responses**

- `401 Unauthorized`: Missing/invalid token
  ```json
  {"error": "Unauthorized"}
  ```

- `422 Unprocessable Entity`: Validation error
  ```json
  {
    "message": "The event field is required.",
    "errors": {
      "event": ["The event field is required."]
    }
  }
  ```

### Idempotency / retries

Requests are **not idempotent**. If your client retries the same request, it may be counted multiple times.

If this matters for a specific event, de-duplicate on the client side (or accept small overcounting for non-critical metrics).

## Code Examples

### PHP (Laravel) — recommended pattern

Store base URL + token in config, and **send via queue/job**.

```php
use Illuminate\Support\Facades\Http;

class HartslagClient
{
    public function send(string $event, int $weight = 1, ?int $userId = null, $createdAt = null): void
    {
        $baseUrl = rtrim(config('services.hartslag.url'), '/');
        $token = config('services.hartslag.token');

        // Never let tracking break the main flow.
        try {
            Http::withToken($token)
                ->acceptJson()
                ->asJson()
                ->connectTimeout(2)
                ->timeout(5)
                ->retry(2, 150, throw: false)
                ->post("{$baseUrl}/api/v1/heartbeat", [
                    'event' => $event,
                    'weight' => $weight,
                    'user_id' => $userId,
                    'created_at' => $createdAt ?? now()->toISOString(),
                ]);
        } catch (\Throwable $e) {
            \Log::warning('Hartslag heartbeat failed', ['error' => $e->getMessage()]);
        }
    }
}
```

**Config** (`config/services.php`):

```php
'hartslag' => [
    'url' => env('HARTSLAG_URL', 'http://hartslag.no'),
    'token' => env('HARTSLAG_TOKEN'),
],
```

**Queue it** (example):

Laravel best practice is to use a dedicated Job that implements `ShouldQueue`.

```bash
php artisan make:job SendHartslagHeartbeat
```

```php
SendHartslagHeartbeat::dispatch('user-registered', auth()->id())
    ->onQueue('low');
```

### JavaScript (Node.js)

```js
const axios = require('axios');

async function sendHeartbeat(baseUrl, token, payload) {
  try {
    const res = await axios.post(
      `${baseUrl.replace(/\/$/, '')}/api/v1/heartbeat`,
      payload,
      {
        timeout: 2000,
        headers: {
          Authorization: `Bearer ${token}`,
          'Content-Type': 'application/json',
        },
      }
    );

    return res.status === 201;
  } catch (err) {
    // Log and continue; tracking must be non-critical.
    return false;
  }
}
```

### Browser/Frontend

Do **not** call the Hartslag API directly from the browser (you would expose your token).

Recommended pattern:

1. Frontend → your backend (no Hartslag token)
2. Backend → Hartslag API (token stored server-side)

## Best Practices

### Event naming

Recommended: descriptive, kebab-case event names.

Good:
- `user-login`
- `payment-completed`
- `email-sent`

Avoid:
- `event1`, `test`, `foo`
- `UserLogin`

### Weight usage

Use `weight` for meaningful metrics:

```php
// Response time in ms
$ms = 150;
$client->send('api-call', weight: $ms);

// Payment amount in cents
$client->send('payment-completed', weight: 1999, userId: $user->id);
```

### Time handling (`created_at`)

- Omit `created_at` for “now” (recommended)
- Use ISO 8601 / RFC 3339 when backdating
- Avoid future timestamps unless you intentionally want that behavior

## Security

- Keep tokens **server-side** (env vars / secrets manager)
- Use HTTPS
- Rotate tokens when needed

## Troubleshooting

### 401 Unauthorized

- Check bearer token
- Check `Authorization: Bearer ...` format

### 422 Validation Error

- Ensure `event` is present and non-empty
- `weight` and `user_id` must be integers if provided
- Use a valid date for `created_at` (ISO 8601 recommended)

### Timeouts / flaky networks

- Use short timeouts (2–5s)
- Retry a small number of times
- Prefer queue/background for non-critical tracking

## Version

API Version: v1
