> ## Documentation Index
> Fetch the complete documentation index at: https://docs.gol.network/llms.txt
> Use this file to discover all available pages before exploring further.

# Errors

> The three error types the SDK throws, and what to retry.

The SDK raises three error types. Two come from a client call, and one comes from webhook verification. Everything else is an `Error` with a stable message from a local check that runs before any request.

| Type                          | Thrown by                                     | Means                                                                                          |
| ----------------------------- | --------------------------------------------- | ---------------------------------------------------------------------------------------------- |
| `GolApiError`                 | Any client method                             | The API returned a non-2xx status. The request reached GOL and was refused                     |
| `GolTransportError`           | Client methods, constructors, polling helpers | The request could not be made, the response was not JSON, or the client was configured wrongly |
| `GolWebhookVerificationError` | `verifyWebhook`                               | A delivery's signature, timestamp, or id did not check out                                     |

`GolApiError` and `GolTransportError` are exported from both entry points. `GolWebhookVerificationError` is exported from `@gol/sdk/server` only, because verification itself is server-only.

## `GolApiError`

```ts theme={null}
import { GolApiError } from "@gol/sdk/server";
import type { GolApiClient } from "@gol/sdk/server";

declare const gol: GolApiClient;
declare const projectId: string;
declare const executionId: string;

try {
  await gol.openGasExecutionDispute(projectId, executionId, "Charged too much.");
} catch (error) {
  if (error instanceof GolApiError) {
    error.status; // the HTTP status
    error.code; // the API's stable code, for program logic
    error.correlationId; // quote this in a bug report
    error.message; // human-readable, not for program logic
  } else {
    throw error;
  }
}
```

Branch on `code`, not on `status` or `message`. The code set is stable across versions; a status can change with the shape of a request and a message is written for people. [Understand API errors](/guides/errors) lists every code and what to check.

| Code                                                                                                        | Retry?                                           |
| ----------------------------------------------------------------------------------------------------------- | ------------------------------------------------ |
| `invalid_request`, `authentication_required`, `authorization_denied`, `not_found`, `capability_unavailable` | No. Fix the cause first                          |
| `conflict`                                                                                                  | No. Refresh the resource and re-decide           |
| `rate_limited`                                                                                              | Yes, after the delay the response indicates      |
| `service_unavailable`, `internal_error`                                                                     | Yes, for a read. See the value-moving rule below |

`correlationId` is also returned in the `x-correlation-id` response header, and the client prefers the one in the body. Log it with the operation you attempted, never with the API key or the full `Authorization` header.

## `GolTransportError`

`GolTransportError` means the SDK never got a usable answer. It has no `code` and no `status`, because there is no response to attribute it to. The original failure is on `cause` when there was one.

```ts theme={null}
import { GolTransportError } from "@gol/sdk/server";
import type { GolApiClient } from "@gol/sdk/server";

declare const gol: GolApiClient;
declare const projectId: string;

try {
  await gol.getGasConfiguration(projectId);
} catch (error) {
  if (error instanceof GolTransportError) {
    console.error("GOL was not reachable", error.cause);
  } else {
    throw error;
  }
}
```

It is also thrown before any request in these cases:

* `baseUrl` is not an absolute URL, uses plain HTTP on a non-local host, or carries credentials, a query string, or a fragment.
* The API key does not match `gol_test_...` or `gol_live_...`.
* A `GolManagementClient` method that needs a session was called with no `sessionToken`.
* `prepareGasExecution` or `submitGasExecution` received an `actionId` that is not 32 bytes of hex.
* The API answered `204` for a route that returns a body, or answered with something that is not JSON.
* A polling helper passed its `timeoutMs`.

Each of those is a bug or a misconfiguration, not a transient condition, so a retry loop will not fix it.

## Retrying a value-moving request

A transport failure is ambiguous: your request may have been fully processed while the answer was lost. That is only safe because the action ID is the idempotency key.

```ts theme={null}
import { GolApiError, GolTransportError } from "@gol/sdk/server";
import type { GolApiClient } from "@gol/sdk/server";
import type { Hex } from "viem";

declare const gol: GolApiClient;
declare const projectId: string;
declare const actionId: Hex;
declare const input: {
  mandateId: string;
  gasPolicyId: string;
  recipient: `0x${string}`;
  amountBaseUnits: string;
  deadline: number;
  maxChargeWei: string;
  agentSignature: Hex;
};

let execution;
for (let attempt = 1; ; attempt++) {
  try {
    // The same action ID and signature on every attempt.
    execution = await gol.submitGasExecution(projectId, actionId, input);
    break;
  } catch (error) {
    const transient =
      error instanceof GolTransportError ||
      (error instanceof GolApiError &&
        ["service_unavailable", "internal_error", "rate_limited"].includes(
          error.code,
        ));
    if (!transient || attempt >= 6) throw error;
    await new Promise((resolve) => setTimeout(resolve, 5_000 * attempt));
  }
}
```

Never mint a new action ID to escape an ambiguous failure, and never assume a failed value-moving call did not happen. Reusing the same ID and signature returns the original execution without moving value again; changing the ID turns a retry into a second transfer.

## Local checks throw plain `Error`

The owner helpers and encoders validate their input before any network call or wallet prompt. These are plain `Error` objects whose message is a stable identifier, not a class, so match on the message.

| Message                                                                                                                             | Where it comes from                                                                                    |
| ----------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
| `prepared_policy_mismatch: <field>`                                                                                                 | `verifyPreparedGasPolicy`, `signPreparedGasPolicy`                                                     |
| `wallet_payload_mismatch`                                                                                                           | `verifyWalletSigningPayload`, `signWalletSigningPayload`, and the revocation and safety-action signers |
| `safety_action_mismatch`                                                                                                            | `signPreparedSafetyAction`                                                                             |
| `wrong_chain`                                                                                                                       | Any function that takes a chain client                                                                 |
| `account_proxy_mismatch`, `account_implementation_mismatch`, `account_implementation_unavailable`                                   | `assertAccountImplementation` and the functions that call it                                           |
| `invalid_policy_recipients`, `invalid_policy_caps`, `invalid_policy_cap`, `duplicate_policy_address`                                | `encodeTransferPolicy`                                                                                 |
| `invalid_project_uuid`                                                                                                              | `contractProjectId`                                                                                    |
| `invalid_digest`, `invalid_user_op_hash`                                                                                            | Digest helpers                                                                                         |
| `invalid_owner_signature`, `invalid_owner_signature_v`, `invalid_safe_owner_signatures`                                             | `packOwnerSignature` and `packOwnerUserOpSignature`                                                    |
| `owner_signer_requires_sign_message`                                                                                                | `signOwnerOperation` for a user operation                                                              |
| `gas_requires_root_mandate`, `mandate_policy_hash_mismatch`, `invalid_gas_policy`, `stale_gas_approval`, `project_binding_mismatch` | `prepareCombinedGasApproval` and `prepareProjectBoundGasApproval`                                      |
| `invalid_gas_revocation`, `gas_policy_unavailable`                                                                                  | `prepareGasRevocation`                                                                                 |
| `safe_previous_module_required`, `invalid_alchemy_entity_id`                                                                        | `preparePermanentRemoval` and `prepareOwnerInstallation`                                               |

A throw means the SDK or the API would not proceed. None of them means the owner's account is unsafe, and none should be resolved by retrying with altered inputs. [Owner signing](/sdk/owner-signing#what-the-helpers-throw) covers the ones you will meet most.

## Webhook verification failures

`verifyWebhook` throws `GolWebhookVerificationError` for exactly four reasons, and all four should be answered with a non-2xx status so GOL retries.

| Message                                      | Check                                                                                                                            |
| -------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| `missing webhook signature headers`          | The `gol-webhook-signature` or `gol-webhook-timestamp` header is absent                                                          |
| `webhook timestamp is outside the tolerance` | The delivery is more than 300 seconds from your clock by default. Pass `toleranceSeconds` to widen it, and check for clock drift |
| `webhook signature is invalid`               | The secret is wrong, or the body was parsed or re-serialized before verification. Verify the raw bytes                           |
| `webhook id mismatch`                        | The body's `id` does not match the `gol-webhook-id` header                                                                       |

```ts theme={null}
import { verifyWebhook } from "@gol/sdk/server";
import { GolWebhookVerificationError } from "@gol/sdk/server";

declare const rawBody: string;
declare const headers: Record<string, string | string[] | undefined>;
declare const secrets: string | readonly string[];

try {
  const event = verifyWebhook(rawBody, headers, secrets);
  console.log(event.id, event.type);
} catch (error) {
  if (error instanceof GolWebhookVerificationError) {
    console.warn("rejected delivery:", error.message);
  } else {
    throw error;
  }
}
```

Signature comparison is timing-safe, and the check runs before the body is parsed, so a forged delivery cannot reach your handler.

## Log without leaking

Keep the operation, the project ID, the action or policy ID, the status, the code, and the correlation ID. Leave out the API key, the developer session token, the webhook secret, and the full `Authorization` header. A correlation ID with no credential beside it is enough for GOL to find the request.
