One of the most complicated aspects of QuickBooks Desktops is its error handling. QuickBooks’s errors are often cryptic and unhelpful, and they can arise from several sources (e.g., Web Connector, QB request processor, QuickBooks Desktop itself), each using a different format and mechanism. Sometimes, their errors do not even describe what went wrong when they could! Conductor unifies and simplifies these errors into a single, consistent error format and augments each with our own user-friendly language that describes how to resolve the issue.

With Conductor, every error is an instance of ConductorError, each with plenty of useful information.

Error types

Any error object you receive will be an instance of one of the following error types:

NameDescription
ConductorIntegrationErrorRaised when the third-party integration encounters an error while processing the end-user’s request. This often results from an issue with the request or data handling that requires your attention to resolve.

E.g., a ListID you provided was not found in QuickBooks Desktop, or an accounting value you supplied did not adhere to the integration’s accounting rules.

Refer to error.integrationCode for the error code returned by the integration, if available.
ConductorIntegrationConnectionErrorRaised when a connection error occurs with the third-party integration on the end-user’s side. This typically indicates an issue with the end-user’s IntegrationConnection or configuration, which they must resolve. In other words, you cannot take action to fix these errors.

E.g., QuickBooks Web Connector (QBWC) failed to connect to QuickBooks Desktop on the end-user’s computer.

Refer to error.integrationCode for the error code returned by the integration connector, if available.

❗ We recommend not triggering alerts for these errors because only the end-user can fix them. See Global error handling for an example of this.
ConductorInvalidRequestErrorRaised when you make an API call with the wrong parameters, in the wrong state, or in an invalid way.
ConductorAuthenticationErrorRaised when Conductor cannot authenticate you with the credentials you provided. E.g., an incorrect API key.
ConductorPermissionErrorRaised when you attempt to access a resource that is not allowed.
ConductorConnectionErrorRaised when there was a network problem between the client (on your server) and Conductor’s servers. E.g., a downed network or a bad TLS certificate.
ConductorInternalErrorRaised when something went wrong on Conductor’s end. (These are rare.)

User-facing error messages

Every ConductorError includes two error messages, and you should use both:

  1. error.message: The technical error message that you should log for debugging.
  2. error.userFacingMessage: The descriptive user-friendly error message explicitly written for the end-user that often contains instructions helping the end-user resolve the issue.

Your app’s UI should display error.userFacingMessage to your end-users for every ConductorError while logging the full error object for your debugging purposes.

For example, see the message and userFacingMessage in the following QBD connection error:

Example ConductorError
{
  // The developer-facing error message, which includes the QBD-provided
  // error code and the QBD-provided error message.
  message: `QBD Connection Error (0x80040420): The QuickBooks user has
    denied access.`,
  // The user-facing error message with instructions for the end-user to
  // resolve the issue.
  userFacingMessage: `We could not connect to QuickBooks Desktop because
    we must re-authorize our connection. To fix this, open QuickBooks
    Desktop, log in as Admin, navigate to "Edit > Preferences >
    Integrated Applications", click "Company Preferences", select our
    app, click "Properties...", ensure "Allow this application to log in
    automatically" is checked, then click "OK". Then try again.`,
  ...
  // Other properties omitted for example.
}

Specific error handling

If you need special handling for specific errors, you can wrap individual API calls, as shown below.

Using async/await:

try {
  const newAccount = await conductor.qbd.account.add(endUserId, {
    Name: "Test Account",
    AccountType: "Bank",
    OpenBalance: "100",
  });
} catch (error) {
  if (error instanceof ConductorError) {
    // Check `error.code`, `error.integrationCode`, etc., for special handling.
  } else {
    // ...
  }
}

Or in the form of a rejected promise:

conductor.qbd.account
  .add(endUserId, {
    Name: "Test Account",
    AccountType: "Bank",
    OpenBalance: "100",
  })
  .then((newAccount) => {
    // ...
  })
  .catch((error) => {
    if (error instanceof ConductorError) {
      // Check `error.code`, `error.integrationCode`, etc., for special handling.
    } else {
      // ...
    }
  });

Global error handling

It is unnecessary to wrap each API call individually, as demonstrated in the examples above. Instead, we suggest implementing a global error handler for your server, such as app.use((error, ...) => { ... }) in Express or formatError in Apollo Server. Within this handler, perform the following actions:

  1. For any ConductorError instance, display the error.userFacingMessage property to the end-user in your app’s UI while logging the complete error object.
  2. For all ConductorError instances, transmit the full error object to your error-tracking service (e.g., Sentry):
    • Send a warning for instances of ConductorIntegrationConnectionError, which are not actionable by you and can only be resolved by the end-user; for example, failure to connect to QuickBooks Desktop on the end-user’s computer.
    • Send an error for all other ConductorError instances, such as an invalid API key.

For example, using an Express error handler:

import * as Sentry from "@sentry/node";
import {
  ConductorError,
  ConductorIntegrationConnectionError,
} from "conductor-node";

app.use((error, req, res, next) => {
  if (error instanceof ConductorError) {
    Sentry.captureException(error, {
      level:
        error instanceof ConductorIntegrationConnectionError
          ? "warning"
          : "error",
    });
    // Return a different error message for your end-user to see in your
    // app's UI.
    res.status(500).send({ error: { message: error.userFacingMessage } });
  } else {
    // ...
  }
});

Or using Apollo Server’s error handler:

import { ApolloServer } from "@apollo/server";
import { unwrapResolverError } from "@apollo/server/errors";
import * as Sentry from "@sentry/node";
import {
  ConductorError,
  ConductorIntegrationConnectionError,
} from "conductor-node";

const server = new ApolloServer({
  // ...
  formatError: (formattedError, error) => {
    const origError = unwrapResolverError(error);
    if (origError instanceof ConductorError) {
      Sentry.captureException(origError, {
        level:
          origError instanceof ConductorIntegrationConnectionError
            ? "warning"
            : "error",
      });
      return {
        ...formattedError,
        // Return a different error message for your end-user to see in
        // your app's UI.
        message: origError.userFacingMessage,
      };
    }
    // ...
    return formattedError;
  },
});