Tenant Webhooks
Fluxir sends a tenant webhook when a service application makes a real status transition. This guide covers tenant configuration, receiver signature verification, and the delivery behavior you should account for.
Configure a tenant endpoint
In the authenticated tenant context, open Travel Agencies Portal > Settings > API Client Management. Webhook settings apply to the tenant you are signed in to; host users, parent tenants, and other tenants cannot use these permissions to manage its endpoint.
Webhook access is controlled by two tenant permissions:
| Permission | Allows |
|---|---|
Webhooks.Configuration.Read | View webhook configuration and open the read-only delivery history. |
Webhooks.Configuration.Manage | Set, change, or remove the endpoint URL, and generate or regenerate the signing key. |
A tenant role that administers webhooks needs both permissions. A role that only views configuration and history needs Webhooks.Configuration.Read only. Assign permissions to tenant roles through the Operational Portal's role and permission management workflow; they are not assigned from the Travel Agencies Portal webhook settings.
To configure the first endpoint, enter the receiver URL and save it. The portal automatically generates the tenant's first signing key as part of this save workflow, then saves the URL. The key is shown once in a dialog. Copy it directly into your receiver's secret manager; Fluxir does not provide a read-back. The backend URL-update operation itself requires a signing key to already exist, which is why the portal coordinates key generation before saving the first URL.
Use an absolute HTTPS URL on port 443 with a DNS hostname. Do not include credentials, a query string, or a fragment. Fluxir validates the destination when it is configured and again when connecting. Keep receiver changes coordinated with the first save: delivery attempts are not retried, so a request received before the receiver has the key can fail permanently.
To change the endpoint, edit the URL and save. To stop future webhook delivery, remove the URL. Removing or changing the endpoint does not reroute queued events; events tied to an older configuration can be marked Skipped.
Signing key and request verification
The signing key is a shared secret. Fluxir uses it to create an HMAC-SHA256 signature over each request, allowing your receiver to authenticate the request and detect changes to its body. The key contains 32 random bytes and is displayed as 64 lowercase hexadecimal characters. Decode the hex to bytes before using it as the HMAC key. Store it securely and never put it in source control, logs, or a URL.
A request is a single POST over HTTPS with Content-Type: application/json; charset=utf-8. Fluxir sends these headers:
| Header | Purpose |
|---|---|
X-Fluxir-Webhook-Id | Delivery ID; it must equal the JSON envelope's eventId. |
X-Fluxir-Webhook-Timestamp | Canonical Unix time in seconds. |
X-Fluxir-Webhook-Key-Id | Identifies the tenant signing-key version. |
X-Fluxir-Webhook-Signature | v1= followed by the lowercase hexadecimal HMAC-SHA256. |
The signature input is the ASCII prefix followed by the exact UTF-8 bytes sent as the request body. Do not parse and reserialize JSON before verification:
ASCII("v1\n" + timestamp + "\n" + delivery_id + "\n") || exact_body_bytes
Compute HMAC-SHA256 with the decoded key bytes. Select the key by the tenant endpoint and key ID, require the header delivery ID to match eventId, reject timestamps more than 300 seconds from receiver time, and compare signatures in constant time. Keep TLS certificate verification enabled. Reject unknown key IDs, invalid signatures, malformed headers, unsupported signature versions, and mismatched event IDs.
The following self-test vector uses synthetic data only; never use this key for a real endpoint. Its signature is for the compact body shown here, byte for byte. Whitespace changes the signature.
key_hex: 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f
timestamp: 1790942400
delivery_id: 550e8400-e29b-41d4-a716-446655440000
signature: v1=4b413fd9e2c39b221614f077a425aee5bf59fbe80dce6f0b2bb5bcd5c854f68d
{"event":"service-application.status-changed","schemaVersion":1,"eventId":"550e8400-e29b-41d4-a716-446655440000","occurredAt":"2026-10-02T12:00:00.1234567Z","tenantId":"4b5e7728-c40f-4d84-9db9-6c9b9bfdd731","payload":{"serviceApplicationId":"9007199254740993","previousState":"Draft","state":"ReadyForReview"}}
For example, the core Python check can be written as follows after your HTTP framework has captured the raw body and validated the timestamp and headers:
signing_input = (
f"v1\n{timestamp}\n{delivery_id}\n".encode("ascii") + raw_body
)
expected = "v1=" + hmac.new(key_bytes, signing_input, hashlib.sha256).hexdigest()
if not hmac.compare_digest(expected, received_signature):
raise ValueError("invalid webhook signature")
A later explicit key regeneration immediately activates the newly generated key for sending. It does not stage activation or provide a sender-side 24-hour overlap. Update the receiver to use the new key as part of a coordinated cutover; requests signed with the previous key may fail, and failed deliveries are terminal. Do not assume a make-before-break or zero-downtime rotation.
Event contract
The only event in this release is service-application.status-changed, schema version 1. Its envelope contains event, schemaVersion, eventId, occurredAt, tenantId, and payload. The v1 payload contains exactly three string values: serviceApplicationId, previousState, and state. The application ID is a base-10 string to avoid JavaScript integer precision loss.
| Field | Type | Meaning |
|---|---|---|
event | string | Stable event name. |
schemaVersion | integer | Envelope version, currently 1. |
eventId | string | Stable event and delivery ID, formatted as a lowercase UUID. |
occurredAt | string | UTC RFC 3339 timestamp recorded for the transition. |
tenantId | string | Owning non-host tenant UUID in lowercase canonical form. |
payload | object | Event-specific values described below. |
| Payload field | Type | Meaning |
|---|---|---|
serviceApplicationId | string | Invariant-culture base-10 service-application ID. |
previousState | string | Exact prior service-application state name. |
state | string | Exact final service-application state name. |
{
"event": "service-application.status-changed",
"schemaVersion": 1,
"eventId": "550e8400-e29b-41d4-a716-446655440000",
"occurredAt": "2026-10-02T12:00:00.1234567Z",
"tenantId": "4b5e7728-c40f-4d84-9db9-6c9b9bfdd731",
"payload": {
"serviceApplicationId": "9007199254740993",
"previousState": "Draft",
"state": "ReadyForReview"
}
}
An event is sent for a real transition to any of these final states: Draft, ReadyForPayment, ReadyForReview, InReview, ReadyForApplication, ReviewFailed, Appointment, Applied, ApplicationRejected, ApplicationConfirmed, Cancelled, Aborted, and InitialApprove. Creating an application is not a transition. No event is sent for an unchanged state, a rejected state precondition, a rolled-back update, a host-owned application, or a tenant without a configured endpoint. Do not add fields or change field meanings while using schema version 1.
Delivery behavior
Fluxir writes the event to its transactional outbox with the status change, then makes one HTTP attempt after commit. There are no retries, no strict ordering guarantee, and no at-least-once delivery guarantee. A timeout or connection loss can leave the receiver's outcome unknown: the receiver may have accepted the request even if Fluxir could not record the response. Use eventId for correlation and make event handling idempotent where practical; receiver-side deduplication is defensive and is not a sender guarantee.
Return a 2xx response only after the event has been durably accepted. Non-2xx responses, timeouts, and connection or destination failures are terminal; Fluxir does not retry them. If the receiver must process the event asynchronously, persist it before acknowledging.
Delivery history and retention
When an endpoint is configured, open View Delivery History from webhook settings. The read-only dialog shows the event name and ID, delivery status, enqueue and attempt times when available, HTTP status and sanitized outcome when available, and the event body. It is scoped to the owning tenant. It has no replay or test-send action.
Successful, failed, and other terminal delivery records are deleted seven days after their terminal completion time. This is bounded operational history, not permanent audit storage. Non-terminal records do not expire on that timer. A history dialog that is already open can continue displaying details it previously loaded until it is refreshed or closed; refresh to load the current server-side history.
The event body can contain service-application identifiers. Protect access to the tenant account and avoid copying body data into logs or public support messages. Production deployment key provisioning and outbound network controls are separate rollout checks; this guide does not assert that a particular deployment has passed them.