Skip to main content
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:

Onboarding events

EventWhen it fires
invitation.createdYou call the Create Invitation endpoint.
invitation.viewedThe fleet opens the magic link in their browser.
invitation.acceptedThe fleet accepts the invitation and begins connecting their TSP(s).
invitation.declinedThe fleet declines the invitation. The payload includes a decline_reason if one was provided.
invitation.expiredThe magic link expired before the fleet acted. You can create a new invitation to retry.
invitation.deletedThe invitation was deleted before it was accepted.
share_agreement.createdA data sharing agreement is created between you and the fleet. This defines which resources you can access.
connection.createdA 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.createdThe 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:
EventWhen it fires
connection.staledThe TSP credentials are no longer valid. Catena stops fetching data until the fleet re-authenticates using the same invitation link.
execution.failedA single data fetch attempt failed (e.g., TSP returned an error or timed out). Catena retries automatically.
schedule.deactivatedThe 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.
The invitation.sent event is also emitted when an invitation is delivered by email, but this only applies if email delivery is configured.

Subscribing to Lifecycle Events

The recommended approach is to create one webhook subscription per event type without any filters using the 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:
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:
# 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.*"}'
Use wildcard patterns like invitation.* to subscribe to all actions for a resource (created, viewed, accepted, etc.) with a single subscription.
Each event payload includes a fleet_id field, so you can route events to the correct fleet in your system. Use your Share Agreements to map fleet_id back to your internal fleet_ref. See the Webhook Setup Guide 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.
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"
  }'
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.

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:
success_redirect_url
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.
failure_redirect_url
string
Where to redirect the fleet if they decline the invitation, the link expires, or an error occurs. The same query parameters are appended.

Example: Handling Lifecycle Events

Here’s how you might handle the key events in your webhook endpoint:
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
Always return 202 Accepted immediately and process events asynchronously. See the Webhook Validation guide to verify event signatures.