Skip to content

Start typing to search the documentation.

SDK error handling

On this page

The SDK throws three error classes. Catch them by type.

SelfApiError

Thrown when the API returns a non-2xx response. It carries:

import { SelfApiError } from '@selfxyz/enterprise-sdk';

try {
  await self.sessions.create({ flowId, externalUuid });
} catch (err) {
  if (err instanceof SelfApiError) {
    err.statusCode; // number (HTTP status)
    err.code; // string ('validation_failed', 'not_found', 'unauthenticated', ...)
    err.message; // string (human-readable)
    err.details; // Record<string, unknown> | undefined
    err.requestId; // string | undefined (X-Request-Id; quote it to support)
  } else {
    throw err; // network error, or something unexpected
  }
}

Switching on the error

Branch on statusCode for the HTTP-level case and on code for the specific reason:

try {
  await self.sessions.create({ flowId, externalUuid });
} catch (err) {
  if (!(err instanceof SelfApiError)) throw err;

  switch (err.statusCode) {
    case 400:
      return badRequest(err.details); // details.issues lists the bad fields
    case 404:
      return notFound('That flow or session no longer exists');
    case 402:
      return paymentRequired(err.details); // { balance, required, planTier }
    case 429:
      return tryLater(); // back off and retry
    default:
      return serverError(err);
  }
}

Error codes

err.code is a stable, machine-readable string. The most common from sessions.create(...):

codestatusCodeWhenWhat to do
validation_failed400Request body didn’t match the schema. details.issues lists offending fields.Fix the request; do not retry.
unauthenticated401Missing, malformed, or revoked API key.Check your key.
unauthenticated402Org credit balance too low (credit gate). details carries balance, required, and planTier.Top up or upgrade. Branch on statusCode 402, not the code.
forbidden403Key recognized but blocked at the key layer (for example a disabled key).Generate a fresh key and retire the old one.
not_found404flowId / sessionId doesn’t exist, is archived, or belongs to another org.Verify the ID in the dashboard.
conflict409The flow exists but has no deployed version; for an on-chain flow, no deployed contract for this environment (details.discriminator: 'no_deployment'); or a live Custom Config session with no active live webhook endpoint (details.discriminator: 'no_webhook_endpoint'), because those flows deliver results only to your webhook, so a session without a receiver is refused.Deploy the flow (and wait for the contract deploy to finish), or register a live webhook endpoint, then retry.
rate_limited429Per-key rate limit exceeded.Honor Retry-After and back off.
vendor_unavailable503A dependency is temporarily unavailable.Retry with backoff.
internal_error500Server-side error.Retry with backoff; if it persists, contact support.
unknown_errorvariesThe API returned an error the SDK couldn’t map to a known code.Inspect statusCode and requestId; retry transient statuses.

SelfValidationError

Thrown when the arguments you pass fail schema validation, before any request goes out. The most common cause is a flowId that isn’t a UUID, or an empty / over-long (>256 chars) externalUuid. It also comes from SelfWebhooks.verify(...) when a verified payload doesn’t match any known event shape.

import { SelfValidationError } from '@selfxyz/enterprise-sdk';

try {
  await self.sessions.create({ flowId, externalUuid });
} catch (err) {
  if (err instanceof SelfValidationError) {
    err.issues; // ZodIssue[], which fields are wrong
    err.message; // e.g. "Invalid sessions.create input: flowId (invalid uuid)"
  }
}

Fix the input; it’s a programming error, not a transient one.

WebhookVerificationError

Thrown by SelfWebhooks.verify(...) when the signature doesn’t match the body, the timestamp is too old, or required headers are missing.

import { WebhookVerificationError } from '@selfxyz/enterprise-sdk';

try {
  const event = SelfWebhooks.verify(raw, headers, secret);
} catch (err) {
  if (err instanceof WebhookVerificationError) {
    // Respond 400; a bad signature won't get better on retry.
  }
}

A separate failure mode: if the signature checks out but the body doesn’t match any known event schema, verify(...) throws SelfValidationError. That usually means the server is sending a newer payload than your SDK version knows; upgrade the SDK. For example, an out-of-date SDK rejects Custom Config events with Invalid enum value ... received 'skipped' on storage_state; the current SDK accepts them.

Retries

The SDK does not retry. A failed request throws immediately. Handle transient responses yourself: retry 429 (after Retry-After) and 5xx with exponential backoff, and never retry a 4xx other than 429.

Logging

Log the status and code so failures are diagnosable:

log.error({
  msg: 'self_api_error',
  statusCode: err.statusCode,
  code: err.code,
  requestId: err.requestId,
  details: err.details,
});
Was this page helpful?