> ## Documentation Index
> Fetch the complete documentation index at: https://docs.catenatelematics.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Inviting Fleets

> How to create invitations, track the onboarding lifecycle with webhooks, and configure callback URLs

When you create an invitation, Catena emits a series of webhook events as the fleet progresses through onboarding. This guide covers the exact sequence of events you can expect and how to subscribe to them.

***

## Webhook Event Lifecycle

When a fleet opens your invitation link and completes onboarding, the following events fire in order:

```mermaid theme={null}
flowchart TD
    created["invitation.created"]
    viewed["invitation.viewed"]
    decision{Fleet decides}
    accepted["invitation.accepted"]
    declined["invitation.declined"]
    expired["invitation.expired"]
    share["share_agreement.created"]
    conn["connection.created (may be skipped)"]
    fleet_conn["fleet_connection.created"]
    data_flow["Data ingestion begins"]
    conn_staled["connection.staled"]
    exec_failed["execution.failed"]
    sched_deact["schedule.deactivated"]

    created --> viewed
    created -.->|Link expires| expired
    viewed --> decision
    decision -->|Accepts| accepted
    decision -->|Declines| declined
    accepted --> share
    share --> conn
    conn --> fleet_conn
    fleet_conn --> data_flow
    data_flow -.->|Credentials expire| conn_staled
    data_flow -.->|Fetch fails| exec_failed
    exec_failed -.->|Consecutive failures| sched_deact

    style created fill:#00bae6,stroke:none,color:#ffffff
    style viewed fill:#00bae6,stroke:none,color:#ffffff
    style accepted fill:#28a745,stroke:none,color:#ffffff
    style declined fill:#dc3545,stroke:none,color:#ffffff
    style share fill:#0083bf,stroke:none,color:#ffffff
    style conn fill:#0083bf,stroke:none,color:#ffffff
    style fleet_conn fill:#0083bf,stroke:none,color:#ffffff
    style expired fill:#dc3545,stroke:none,color:#ffffff
    style decision fill:#ffc107,stroke:none,color:#00354d
    style data_flow fill:#6c757d,stroke:none,color:#ffffff
    style conn_staled fill:#dc3545,stroke:none,color:#ffffff
    style exec_failed fill:#dc3545,stroke:none,color:#ffffff
    style sched_deact fill:#dc3545,stroke:none,color:#ffffff
```

### Onboarding events

| Event                      | When it fires                                                                                                                                                                                                                     |
| -------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `invitation.created`       | You call the [Create Invitation](/api-reference/organizations-api) endpoint.                                                                                                                                                      |
| `invitation.viewed`        | The fleet opens the magic link in their browser.                                                                                                                                                                                  |
| `invitation.accepted`      | The fleet accepts the invitation and begins connecting their TSP(s).                                                                                                                                                              |
| `invitation.declined`      | The fleet declines the invitation. The payload includes a `decline_reason` if one was provided.                                                                                                                                   |
| `invitation.expired`       | The magic link expired before the fleet acted. You can create a new invitation to retry.                                                                                                                                          |
| `invitation.deleted`       | The invitation was deleted before it was accepted.                                                                                                                                                                                |
| `share_agreement.created`  | A data sharing agreement is created between you and the fleet. This defines which resources you can access.                                                                                                                       |
| `connection.created`       | A connection is established between the fleet and a TSP. **May be skipped** if the fleet already has an existing connection to that TSP — Catena reuses connections so data is only ingested once, even across multiple partners. |
| `fleet_connection.created` | The connection is associated with the fleet. This is the final event — data ingestion begins shortly after.                                                                                                                       |

### Operational events

After a fleet is connected and data is flowing, these events indicate issues with the connection or data ingestion:

| Event                  | When it fires                                                                                                                                                                                           |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `connection.staled`    | The TSP credentials are no longer valid. Catena stops fetching data until the fleet re-authenticates using the same invitation link.                                                                    |
| `execution.failed`     | A single data fetch attempt failed (e.g., TSP returned an error or timed out). Catena retries automatically.                                                                                            |
| `schedule.deactivated` | The data fetch schedule has been disabled after repeated failures exceed the consecutive error threshold. No further data will be ingested until the issue is resolved and the schedule is reactivated. |

<Info>
  The `invitation.sent` event is also emitted when an invitation is delivered by email, but this only applies if email delivery is configured.
</Info>

***

## Subscribing to Lifecycle Events

The recommended approach is to create **one webhook subscription per event type without any filters** using the [Notifications API](/api-reference/notifications-api). This gives you a single, organization-wide stream of events that covers all fleets — both current and future ones you onboard.

For example, to receive all invitation events:

```bash theme={null}
curl -X POST 'https://api.catenatelematics.com/v2/notifications/webhooks' \
  -H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{
    "url": "https://api.yourapp.com/webhooks/catena",
    "event_name": "invitation.*"
  }'
```

Repeat for the other lifecycle event types you need:

```bash theme={null}
# Share agreement events
curl -X POST '...' -d '{"url": "https://api.yourapp.com/webhooks/catena", "event_name": "share_agreement.*"}'

# Connection events
curl -X POST '...' -d '{"url": "https://api.yourapp.com/webhooks/catena", "event_name": "connection.*"}'

# Fleet-connection events
curl -X POST '...' -d '{"url": "https://api.yourapp.com/webhooks/catena", "event_name": "fleet_connection.*"}'
```

<Tip>
  Use wildcard patterns like `invitation.*` to subscribe to all actions for a resource (created, viewed, accepted, etc.) with a single subscription.
</Tip>

Each event payload includes a `fleet_id` field, so you can route events to the correct fleet in your system. Use your [Share Agreements](/api-reference/organizations-api) to map `fleet_id` back to your internal `fleet_ref`.

See the [Webhook Setup Guide](/api-reference/webhooks/webhook-setup) for details on creating subscriptions, filtering, and signature verification.

### Alternative: Using `callback_url`

If you prefer not to manage webhook subscriptions manually, you can set a `callback_url` when creating the invitation. Catena automatically creates webhook subscriptions for the lifecycle events scoped to that specific invitation.

```bash theme={null}
curl -X POST 'https://api.catenatelematics.com/v2/orgs/invitations' \
  -H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{
    "fleet_ref": "FLEET-42",
    "fleet_name": "Acme Trucking",
    "callback_url": "https://api.yourapp.com/webhooks/catena",
    "success_redirect_url": "https://app.yourapp.com/onboarding/success",
    "failure_redirect_url": "https://app.yourapp.com/onboarding/failure"
  }'
```

<Warning>
  If you already have organization-wide webhook subscriptions for the same event types, using `callback_url` will result in **duplicate events**. In that case, omit the `callback_url` and rely on your existing subscriptions.
</Warning>

***

## Redirect URLs

In addition to webhooks, you can configure redirect URLs to send the fleet back to your application after they complete (or fail) onboarding:

<ParamField body="success_redirect_url" type="string">
  Where to redirect the fleet after successful onboarding. Catena appends `?invitation_id={id}&fleet_ref={ref}` as query parameters so you can identify which fleet completed onboarding.
</ParamField>

<ParamField body="failure_redirect_url" type="string">
  Where to redirect the fleet if they decline the invitation, the link expires, or an error occurs. The same query parameters are appended.
</ParamField>

***

## Example: Handling Lifecycle Events

Here's how you might handle the key events in your webhook endpoint:

<CodeGroup>
  ```python Python theme={null}
  from flask import Flask, request, jsonify

  app = Flask(__name__)

  @app.route("/webhooks/catena", methods=["POST"])
  def handle_webhook():
      event = request.json
      event_name = event["event_name"]

      if event_name == "invitation.viewed":
          # Fleet opened the link — update your UI
          print(f"Fleet viewed invitation {event['data'][0]['id']}")

      elif event_name == "invitation.accepted":
          # Fleet accepted — a share agreement will follow
          invitation = event["data"][0]
          print(f"Fleet {invitation.get('fleet_ref')} accepted!")

      elif event_name == "invitation.declined":
          invitation = event["data"][0]
          print(f"Fleet declined: {invitation.get('decline_reason')}")

      elif event_name == "share_agreement.created":
          agreement = event["data"][0]
          print(f"Share agreement created for fleet {agreement['fleet_id']}")
          print(f"Scopes: {agreement['scopes']}")

      elif event_name == "fleet_connection.created":
          # Final event — data ingestion starts soon
          conn = event["data"][0]
          print(f"Fleet {conn['fleet_id']} connected via {conn['connection_id']}")

      return jsonify({}), 202
  ```

  ```javascript Node.js theme={null}
  app.post("/webhooks/catena", (req, res) => {
    const { event_name, data } = req.body;

    switch (event_name) {
      case "invitation.viewed":
        console.log(`Fleet viewed invitation ${data[0].id}`);
        break;

      case "invitation.accepted":
        console.log(`Fleet ${data[0].fleet_ref} accepted!`);
        break;

      case "invitation.declined":
        console.log(`Fleet declined: ${data[0].decline_reason}`);
        break;

      case "share_agreement.created":
        console.log(`Share agreement for fleet ${data[0].fleet_id}`);
        console.log(`Scopes: ${JSON.stringify(data[0].scopes)}`);
        break;

      case "fleet_connection.created":
        console.log(`Fleet ${data[0].fleet_id} connected via ${data[0].connection_id}`);
        break;
    }

    res.status(202).send();
  });
  ```
</CodeGroup>

<Tip>
  Always return `202 Accepted` immediately and process events asynchronously. See the [Webhook Validation](/api-reference/webhooks/webhook-validation) guide to verify event signatures.
</Tip>
