# Integration errors and fixes

These are the errors developers and coding agents most often meet while building a BoldSign integration in the sandbox. Each one gives the exact response, why it happens, and the fix. For the meaning of each HTTP status code, see [Possible errors and solutions](/troubleshooting/possible-errors-and-solution/).

## Before you start: confirm the key and region

Make one read-only call before building anything:

```shell
curl -i https://api.boldsign.com/v1/brand/list -H "X-API-KEY: $BOLDSIGN_API_KEY"
```

`200` means the key and the base URL match. A new sandbox account has no brands, so `{"result":[]}` is a normal answer.

## 401 Unauthorized on every call

**Response:** `401`, on every endpoint.

**Cause:** the API key is wrong or revoked, or the base URL belongs to another region. A key from a US account gets `401` from `api-eu.boldsign.com`, `api-ca.boldsign.com` and `api-au.boldsign.com`.

**Fix:**
1. Check the key under **API > API Key** in the account the key came from.
2. Call `GET /v1/brand/list` on each regional host:
   - US: `https://api.boldsign.com`
   - EU: `https://api-eu.boldsign.com`
   - CA: `https://api-ca.boldsign.com`
   - AU: `https://api-au.boldsign.com`
3. Use the host that returns `200`.

## 403 "Invalid Document ID" right after sending

**Response:**

```json
{"error":"Invalid Document ID"}
```

This comes back from `GET /v1/document/getEmbeddedSignLink` or `GET /v1/document/properties` within a second or two of `POST /v1/document/send` returning that same `documentId`.

**Cause:** sending is asynchronous. The API returns the document ID at once, and the document becomes available a few seconds later; usually within a few seconds. The ID is not wrong.

**Fix:** retry the next call briefly, 3 to 5 times, 2 to 3 seconds apart, before treating it as an error. For a definite answer, listen for the `Sent` or `SendFailed` webhook.

```typescript
async function retrySoon<T>(fn: () => Promise<T>, tries = 5): Promise<T> {
  for (let i = 1; ; i++) {
    try { return await fn(); }
    catch (err: any) {
      const status = err?.statusCode ?? err?.response?.status; // Node SDK HttpError or Axios error
      if (i >= tries || (status !== 400 && status !== 403)) throw err;
      await new Promise((r) => setTimeout(r, 2000 * i));
    }
  }
}
const { signLink } = await retrySoon(() => documentApi.getEmbeddedSignLink(documentId, signerEmail));
```

## 400 "Invalid template id." right after creating a template

**Response:**

```json
{"error":"Invalid template id."}
```

This comes back from `POST /v1/template/send?templateId=...` within a second of `POST /v1/template/create` returning that template ID.

**Cause:** template creation is also asynchronous.

**Fix:** wait for the `TemplateCreated` webhook, or retry briefly as above. In production you normally send from a template created earlier, so this mostly affects tests and set-up scripts.

## 403 "Forbidden" that does not go away

**Response:**

```json
{"error":"Forbidden"}
```

This comes back from `GET /v1/document/properties` for a document the same key sent, still minutes later.

**Cause:** the key cannot see that document. BoldSign returns the same `403 {"error":"Forbidden"}` for a document ID that does not exist, so either the send failed after returning the ID (look for a `SendFailed` event), or the document belongs to another account, environment or team than the key.

**Fix:**
1. In the BoldSign web app, open **Documents** in the account the key came from, and look for the document.
2. Check **API > API Key**: the key's Environment must be the one you expect (Sandbox or Live).
3. For integration and coding-agent testing, use a dedicated developer sandbox account (https://account.boldsign.com/signup?planId=1076).

## 429 Too Many Requests

**Response:** `429` with a `Retry-After` header.

**Cause:** the sandbox allows 50 requests per hour per account, counted across all keys and OAuth apps.

**Fix:**
- wait for `Retry-After`;
- use webhooks instead of polling `properties`;
- in tests, mock the API rather than calling it in a loop.

## Your webhook endpoint rejects real BoldSign events

**Symptom:** your endpoint returns `401` (or your signature check fails) for events BoldSign really sent.

**Causes:**
- The HMAC was computed over the body alone. The signed message is the timestamp, a full stop, then the raw body.
- The signature was compared with the whole `X-BoldSign-Signature` header, instead of with `s0` and `s1`.
- The framework parsed the JSON before the check, so the bytes changed.
- The secret is not this webhook's own secret (each webhook has its own; see **Reveal**).
- The server clock is more than 5 minutes off, so the timestamp check fails.

**Fix**, as described in [Verify webhook events](/webhooks/verify-webhook-events/):

```typescript
import crypto from 'crypto';

export function verifyBoldSignSignature(rawBody: Buffer, header: string | undefined, secret: string): boolean {
  if (!header || !secret) return false;
  let t = '';
  const signatures: string[] = [];
  for (const part of header.split(',')) {                  // "t=1700000000, s0=ab12..., s1=cd34..."
    const i = part.indexOf('=');
    if (i < 0) return false;
    const key = part.slice(0, i).trim(), value = part.slice(i + 1).trim();
    if (key === 't') t = value; else if (/^s\d+$/.test(key)) signatures.push(value.toLowerCase());
  }
  if (!/^\d+$/.test(t) || signatures.length === 0) return false;
  if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false;   // replay protection
  const expected = crypto.createHmac('sha256', secret).update(Buffer.concat([Buffer.from(`${t}.`), rawBody])).digest();
  return signatures.some((s) => /^[0-9a-f]{64}$/.test(s) && crypto.timingSafeEqual(Buffer.from(s, 'hex'), expected));
}
```

The Node SDK has the same check in `boldsign/dist/WebhookUtility` (not exported from the package root). It throws `RangeError` if a signature has the wrong length, so wrap it in `try/catch`.

## The webhook was added but BoldSign's check fails

**Cause:** when you add a webhook, BoldSign sends a request with the header `X-BoldSign-Event: Verification`, and your endpoint must answer `200`. Endpoints that run the signature check first, or that look for the event type in the body, reject it.

**Fix:** check the header first and return `200` straight away:

```typescript
app.post('/webhooks/boldsign', express.raw({ type: '*/*' }), (req, res) => {
  if (req.get('X-BoldSign-Event') === 'Verification') return res.sendStatus(200);
  // ...then verify the signature
});
```

## The webhook handler cannot find the document in the event

**Cause:** the code reads `payload.document`. In the event payload, the subject is in `payload.data`, and `data.object` says what it is (`document`, `template`, `senderIdentity`, `identityVerification`).

**Fix:** read `payload.event.eventType` for the event and `payload.data.documentId` for the document. See [Sample event data](/webhooks/sample-event-data/).

## No webhook ever arrives during local development

**Causes:**
- BoldSign cannot reach `localhost`.
- BoldSign does not replay events from before the webhook was added.

**Fix:** expose the app first with `cloudflared tunnel --url http://localhost:3000` ([Listen for callback](/webhooks/listen-for-callback/)). Register the HTTPS address under **API > Webhooks**, then send a new document.

## Text tags do not become fields

**Causes:**
- `useTextTags` was not set to `true`.
- The tags use another syntax.
- The tags are images, not text, in the PDF.

**Fix:** the syntax is `{{fieldType|signerIndex|required}}`, for example `{{sign|1|*}}` or `{{date|1|*}}`. Send with `useTextTags: true` and leave out `formFields`. Spaces inside the braces widen the field, and a larger font makes it taller. White text hides the tag on the signed copy. See [Text tags](/text-tags/introduction/).

## "This document's state has been modified" when the signer presses Finish

**Message:** "This document's state has been modified after you had initiated the signing process so please reload the document to continue signing."

**Cause:** the signing page was opened within seconds of the send, while BoldSign was still processing the new document.

**Fix:** reload the page and sign again. In an app, open the signing page only after the `Sent` webhook arrives, or a few seconds after the send. A signer who opens the email is not affected.

## Fields appear in the wrong place

**Cause:** `bounds` were given in PDF points. Bounds are measured from the top-left of the page in pixels at 96 per inch (1 point = 1.333 px), so point values land too high and too far left.

**Fix:** multiply point values by 1.333, or use text tags.

## The signer receives no email

**Causes:**
- `disableEmails: true` was set. That is expected for embedded signing, where the signer signs only through the embedded link.
- The address bounced.

**Fix:** for embedded signing, get a fresh link with `getEmbeddedSignLink` on each visit. For test signers, use `<anyname>@boldsign.dev` addresses.

## Complete working examples

The tested starter recipes (send a document, embedded signing, send from a template, with the SDK version pinned) handle every case on this page. They are in the `recipes/` folder of the BoldSign integration skill: https://github.com/boldsign/boldsign-esignature-skills
