Skip to main content

Handle errors, retries, and terminal states

Public Widget failures use a normalized safe shape. Applications may log the correlation ID and stable code, but must not display raw provider errors, stack traces, tokens, internal IDs, or topology.

{
"code": "RateLimited",
"retryable": true,
"terminal": false,
"retryAfter": 3,
"correlationId": "<YOUR_CORRELATION_ID>"
}

Use retryable and terminal as contract signals. A 429 may include Retry-After; respect it and apply the package’s bounded backoff. Do not retry a non-idempotent operation with a new key after an ambiguous response.

Duplicate-safe message retry

The original operation identifier and idempotency key identify one logical message. Retry only after the current failure version is known and preserve the original key:

POST /api/integrations/v1/widget/messages HTTP/1.1
Origin: https://www.example.invalid
X-Qanivo-Widget-Version: 1.0
Authorization: Bearer <YOUR_ACCESS_GRANT>
Idempotency-Key: <YOUR_CLIENT_OPERATION_ID>
Content-Type: application/json

{
"clientOperationId": "<YOUR_CLIENT_OPERATION_ID>",
"body": "Hello from the website",
"contentType": "text"
}

The current package retries by repeating POST /messages with the same clientOperationId and idempotency key. The authority contract also defines a dedicated /messages/{clientOperationId}/retry target, but it is not exposed by the current package/controller surface; do not call that target until the installed client and server advertise it. Only retry a failure that the contract classifies as retryable or that the user explicitly requests. A duplicate request with the same input returns the canonical prior result; reuse with different input is a conflict.

Outbox states

The package exposes message delivery states such as pending, queued, retrying, sent, delivered, read, and failed. Retry exhaustion is reported through message.retry.exhausted and its safe error, not as an invented deliveryStatus value. Keep a visible action for a user-retryable failure and a clear explanation for an unsupported or terminal outcome.

widget.on('message.retrying', ({ message, attempt }) => {
showRetryNotice(message.clientOperationId, attempt);
});

widget.on('message.retry.exhausted', ({ message }) => {
showManualRetry(message.clientOperationId);
});

widget.on('error', (error) => {
reportSafeFailure({ code: error.code, recoverable: error.recoverable });
});

HTTP error envelopes may include a safe correlationId; runtime WidgetErrorDetail values do not necessarily carry that field, so correlate HTTP diagnostics at the transport boundary.

Terminal session states

Stop automatic session/message/realtime retries for session.expired, session.revoked, visitor.banned, connection.disabled, replay.reset.required when reauthentication is required, and realtime terminal failure. Prompt the visitor with an actionable next step such as refresh, sign in, or contact support. Never turn a terminal denial into an anonymous send.

Transient offline submission can remain in the package outbox when the configured policy permits it. offline.submitted means the browser accepted a bounded local submission; it does not mean Qanivo delivered a message. Show the eventual state and provide a safe discard/retry path.

Error handling checklist

  • Preserve clientOperationId, messageId, sequence, and correlation ID in local UI state.
  • Treat Retry-After as a delay hint, not permission to bypass expiry, ban, consent, suppression, origin, or payload policy.
  • Use one bounded retry policy for reconnect and renewal; stop at terminal events.
  • Redact request bodies, access grants, identity assertions, screenshot bytes, and provider diagnostics from logs.
  • Keep user-facing text localized and safe; expose stable codes only where they help the developer or visitor act.