# API v1 Webhooks

## Purpose

This document describes the public webhook layer for the FlowMint container tracking API.

The webhook layer is intentionally thin:
- FlowNUC collects source evidence
- FlowMint normalizes truth
- webhooks push the same normalized tracking resource to downstream consumers

## Base URL

- Target public domain: `https://api.cargoflowplus.com`
- Temporary production base URL: `https://flowmint.cargoflowplus.com`

## Authentication

Webhook subscription management uses the same consumer token as the pull API:

```text
Authorization: Bearer <consumer-token>
```

## Endpoints

### Create webhook subscription

`POST /api/v1/subscriptions/webhooks`

Example:

```json
{
  "endpoint_url": "https://client.example.com/webhooks/flowmint",
  "signing_secret": "replace-me-with-a-random-secret",
  "enabled": true,
  "event_filters": ["container.updated", "container.completed"],
  "metadata": {
    "environment": "production"
  }
}
```

### List subscriptions

`GET /api/v1/subscriptions`

### Update subscription

`PATCH /api/v1/subscriptions/{target_id}`

### Delete subscription

`DELETE /api/v1/subscriptions/{target_id}`

## Public event filters

Recommended event filters in v1:

- `container.updated`
  Fired when the normalized current status, ETA, source, or milestones change.
- `container.completed`
  Fired when `journey_state` reaches a completed phase such as `returned` or `delivered`.

## Payload envelope

Webhook payloads use a small, stable envelope:

```json
{
  "event": "container.updated",
  "occurred_at": "2026-04-13T10:18:00Z",
  "tracking_id": "WATCH-CONTAINER-20260413-ABC123",
  "container_number": "TXGU7408862",
  "journey_state": "returned",
  "data": {
    "status_url": "/api/v1/containers/TXGU7408862",
    "timeline_url": "/api/v1/containers/TXGU7408862/timeline"
  }
}
```

Recommended semantics:
- `event`
  Short event type such as `container.updated` or `container.completed`
- `occurred_at`
  When FlowMint emitted the normalized event
- `tracking_id`
  Stable FlowMint tracking watch id
- `container_number`
  Container identifier for easy client correlation
- `journey_state`
  Concise phase field such as `in_transit`, `at_terminal`, `left_terminal`, `delivered`, or `returned`
- `data.status_url`
  Relative resource link for current state
- `data.timeline_url`
  Relative resource link for milestones

## Resource payloads

Webhook payloads should be treated as a push representation of the public API resources:

- current view: `GET /api/v1/containers/{container_number}`
- timeline view: `GET /api/v1/containers/{container_number}/timeline`

The normalized tracking resources remain the source of truth. The webhook envelope is only the delivery wrapper.

### Example `container.updated`

```json
{
  "event": "container.updated",
  "occurred_at": "2026-04-13T10:18:00Z",
  "tracking_id": "WATCH-CONTAINER-20260413-ABC123",
  "container_number": "TXGU7408862",
  "journey_state": "left_terminal",
  "data": {
    "status_url": "/api/v1/containers/TXGU7408862",
    "timeline_url": "/api/v1/containers/TXGU7408862/timeline"
  }
}
```

### Example `container.completed`

```json
{
  "event": "container.completed",
  "occurred_at": "2026-04-13T11:04:00Z",
  "tracking_id": "WATCH-CONTAINER-20260413-ABC123",
  "container_number": "TXGU7408862",
  "journey_state": "returned",
  "data": {
    "status_url": "/api/v1/containers/TXGU7408862",
    "timeline_url": "/api/v1/containers/TXGU7408862/timeline"
  }
}
```

## Signing

- `signing_secret` is scoped to the webhook target
- downstream consumers should verify the request signature before trusting the payload

## Delivery model

- pull API remains available at all times
- webhooks provide push-first delivery for the same normalized tracking contract
- webhook delivery should not force clients to parse raw source-specific events
