# Troubleshooting requests

Rejected calls, invitations that never arrived, and callbacks that never landed.

## Troubleshooting requests

Where to look when a request was refused, an invitation never arrived, or a workflow is still waiting for a callback that already happened.

<h2 id="reading-errors">Reading an error</h2>

<p>
  Every rejection carries two things: a <code>code</code> for the kind of failure, and a <code>details.reason</code> for the specific cause.
  Branch on the code; read the reason to know what to fix. Where it helps, <code>details</code> also names the offending <code>field</code>,
  the keys that would have been accepted, or the option keys a choice question takes.
</p>

<p>
  The request methods use four codes: <code>VALIDATION_ERROR</code> (the call was wrong), <code>CONFLICT</code> (the request is in the wrong
  state, or an idempotency key was reused), <code>NOT_FOUND</code>, and <code>UPGRADE_REQUIRED</code> (a plan gate or the monthly
  allowance). Calling too fast returns <code>RATE_LIMITED</code> instead, with <code>retryAfterMs</code> — see{' '}
  <a href="/requests/creating-requests#rate-limit">the rate limit</a>.
</p>

```
{
  "ok": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "No field with key \"company\".",
    "details": { "reason": "UNKNOWN_FIELD_KEY", "field": "prefill.company", "validKeys": ["company_name", "company_size"] }
  }
}
```

<h2 id="reasons">Reasons you might see</h2>

<h3 id="reasons-setup">Getting the call right</h3>

<h3 id="reasons-state">Acting on an existing request</h3>

<h2 id="invitation-problems">The invitation never arrived</h2>

<p>Open the request in the Requests page and read the timeline. The first invitation line tells you which case you are in.</p>

> ℹ️ **No timeline entry at all**
> <p>
>     Then no email was ever asked for. The request was created with <code>delivery: "none"</code> — deliver the link yourself, or create a
>     new request with <code>delivery: "email"</code>.
>   </p>

<p>
  Invitations and reminders share a budget of ten emails per day per form and recipient address, and a request never emails its recipient
  more than nine times in its life — one invitation and up to eight reminders.
</p>

<h2 id="callback-problems">The callback never landed</h2>

<p>
  The request drawer's <strong>Callback</strong> section shows the URL and the outcome. <em>Callback failed after N attempts</em> means
  formbase tried and gave up — eight attempts over about four hours.
</p>

<ol>
  <li>
    <strong>Check the URL.</strong> It is shown in the drawer. A workflow tool's resume URL belongs to one run, and a run that was deleted
    or re-created no longer answers on it.
  </li>
  <li>
    <strong>Check what your endpoint returned.</strong> Anything outside 2xx is a failure. A 4xx other than 408 or 429 stops the retries
    immediately — formbase reads it as "your endpoint rejected this", and re-sending identical bytes cannot change that.
  </li>
  <li>
    <strong>Fix the receiver, then press Replay.</strong> The same payload goes out again with the same event id, so a receiver that
    deduplicates is safe.
  </li>
</ol>

> ⚠️ **Signature check failing?**
> <p>
>     Almost always the raw body. If you parse the JSON and re-serialize it before hashing, the bytes differ and the signature will never
>     match. Hash the body exactly as it arrived. The other common cause is a regenerated signing secret that the receiver has not picked up —
>     there is no grace period.
>   </p>

<h2 id="other">Other things people hit</h2>

<ul>
  <li>
    <strong>The recipient says the link shows a notice, not the form.</strong> The request is terminal — completed, expired, or canceled.
    That is the outcome page. Create a new request if they need another go.
  </li>
  <li>
    <strong>An automation stopped matching after an edit</strong> — an answer went missing from the callback, or{' '}
    <code>requests.create</code> began refusing a key with <code>UNKNOWN_FIELD_KEY</code>. A published field key disappeared: someone
    retyped it, or deleted the question and added a new one in its place. Retitling is safe; both of those are not. Type the old key on the
    field (toolbar key icon → <strong>Keys</strong>) and publish again. Publish warns before this happens — see{' '}
    <a href="/requests/field-keys#removed-keys">When a published key is about to disappear</a>.
  </li>
  <li>
    <strong>Prefill for a file upload or a signature is refused.</strong> Those cannot be supplied by a caller — <code>fields.list</code>{' '}
    marks them <code>prefillable: false</code>.
  </li>
  <li>
    <strong>Two requests appeared for one workflow run.</strong> The run retried without an <code>idempotencyKey</code>. Pass the execution
    id as the key.
  </li>
</ul>

<div class="not-prose grid gap-3 sm:grid-cols-2">
  - [Callbacks & signing](/requests/callbacks) — Retries, replay, and how to verify.
  - [The Requests page](/requests/managing-requests) — Where the timeline and the actions live.
</div>
