Reading curing statuses
Read OPEN, IN_REVIEW, and RESOLVED curing statuses from the hook, server-side, and webhooks.
Curing statuses live on each issue inside statusData.wFormQuestionnaire.issues. The useTaxbit hook exposes them through statusData, and the same data is available server-side via the status endpoint and the ACCOUNT_OWNER_TAX_DOCUMENTATION_STATUS webhook. This page covers how to read each status, how to keep the data fresh, and how to handle the lifecycle outside the browser.
For the embedding walkthrough, see the Curing Integration Guide. For the full hook signature and return shape, see the Component and Hook Reference.
The hook makes one call to GET /tax-documentation-status on mount and exposes the response as statusData. Curing statuses are per-issue:
statusData?.wFormQuestionnaire?.issues
// Array of:
// {
// issueType: 'US_INDICIA' | 'TREATY_COUNTRY_MISMATCH' | …,
// status: 'OPEN' | 'IN_REVIEW' | 'RESOLVED',
// createdAt: '2026-06-15T17:42:11Z',
// details: [{ field, description }, …],
// }A convenience boolean, needsCuringDocumentation, is also exposed on the hook — it's the roll-up of "is there at least one OPEN curable issue?". For full lifecycle visibility (in-review state, resolved history), read issues[] directly.
const {
statusData, // the full status object
needsCuringDocumentation, // boolean — OPEN curable issue exists
isLoading, // true while the status fetch is in flight
refresh, // re-fetch status + submission, returns Promise<void>
refreshStatus, // re-fetch status only
error, // fetch error, if any
} = useTaxbit({ bearerToken, questionnaire: 'W-FORM' });Curable issue types are the four W-8 issues the SDK's curing flow can resolve. Define this set once and reuse it for filtering:
const CURABLE_ISSUE_TYPES = new Set([
'US_INDICIA',
'TREATY_COUNTRY_MISMATCH',
'CARE_OF_PERMANENT_ADDRESS',
'PO_BOX_PERMANENT_ADDRESS',
]);Open issues — the filer needs to act
const openCurableIssues = (statusData?.wFormQuestionnaire?.issues ?? [])
.filter((issue) => CURABLE_ISSUE_TYPES.has(issue.issueType))
.filter((issue) => issue.status === 'OPEN');Use this to drive a "your tax documentation needs attention" badge in your account UI or to gate the <TaxbitCuringDocumentation /> mount. Equivalent to checking needsCuringDocumentation, but exposes the actual issue list so you can show the filer what's outstanding before they land on the curing page.
In-review issues — the filer has submitted, your reviewer needs to act
const inReviewIssues = (statusData?.wFormQuestionnaire?.issues ?? [])
.filter((issue) => CURABLE_ISSUE_TYPES.has(issue.issueType))
.filter((issue) => issue.status === 'IN_REVIEW');Use this to show "You've submitted your documentation — we're reviewing it" in your filer-facing UI. The <TaxbitCuringDocumentation /> widget renders nothing while a filer's curable issues are all IN_REVIEW, so you'll want your own confirmation surface during this window.
Resolved issues — closed, audit history
const resolvedIssues = (statusData?.wFormQuestionnaire?.issues ?? [])
.filter((issue) => CURABLE_ISSUE_TYPES.has(issue.issueType))
.filter((issue) => issue.status === 'RESOLVED');Useful for compliance audit views or for showing a filer their history. The status will not change again from RESOLVED.
All statuses in one pass
const issuesByStatus = (statusData?.wFormQuestionnaire?.issues ?? []).reduce(
(acc, issue) => {
if (!CURABLE_ISSUE_TYPES.has(issue.issueType)) return acc;
(acc[issue.status] ??= []).push(issue);
return acc;
},
{} // { OPEN?: [...], IN_REVIEW?: [...], RESOLVED?: [...] }
);Single reduce, all three buckets. Drop straight into a filer dashboard summary.
The hook reads the status endpoint once on mount. After a successful cure submission, the server-side status has changed but statusData won't reflect it until you re-fetch. Wrap the SDK's onSuccess callback:
<TaxbitCuringDocumentation
bearerToken={bearerToken}
onSuccess={async () => {
await refresh();
router.push('/account');
}}
/>refresh() returns a Promise<void> that resolves when both the status and submission fetches settle, so you can await it before reading derived state. Use refreshStatus() if you only need the status object updated.
useTaxbit is a React hook — it only runs while the filer has a browser tab open. For lifecycle awareness when the filer isn't online (a reviewer just resolved a cure, a state change happened overnight), read the status from the server.
The endpoint is the same one the hook calls. Your backend hits it directly with the Account Owner–scoped bearer token:
GET /tax-documentation-status
Authorization: Bearer <accountOwnerToken>
The response is the snake_case server shape (w_form_questionnaire.issues[].status). Apply the same filtering logic — the field names and enum values are identical to the client shape.
For systematic awareness without polling, subscribe to the ACCOUNT_OWNER_TAX_DOCUMENTATION_STATUS webhook. It fires on any status change to the documentation object, including:
- Filer submits a cure → an issue flips
OPEN→IN_REVIEW - Reviewer resolves a cure →
IN_REVIEW→RESOLVED - Reviewer requests additional documentation →
IN_REVIEW→OPEN - The underlying W-Form expires or has a change-in-circumstances → new issues are added
A handler should:
- Fetch the current status object for the
account_owner_idfrom the payload. - Diff against your last-known state.
- Route the right notification — email the filer when an issue is back to
OPEN, notify your tax-ops team when something flipped toIN_REVIEW, update your compliance ledger onRESOLVED.
app.post('/webhooks/taxbit', async (req, res) => {
const { event_type, account_owner_id } = req.body;
if (event_type !== 'ACCOUNT_OWNER_TAX_DOCUMENTATION_STATUS') {
return res.sendStatus(200);
}
const status = await fetchTaxDocStatus(account_owner_id);
const issues = status?.w_form_questionnaire?.issues ?? [];
const open = issues.filter((i) => i.status === 'OPEN' && CURABLE.has(i.issue_type));
const inReview = issues.filter((i) => i.status === 'IN_REVIEW' && CURABLE.has(i.issue_type));
const resolved = issues.filter((i) => i.status === 'RESOLVED' && CURABLE.has(i.issue_type));
await onStatusChange(account_owner_id, { open, inReview, resolved });
res.sendStatus(200);
});The webhook fires for any state change on the documentation object, not just cures. Don't trigger filer-facing notifications directly from the event — diff against your last-known state in your own database first. See the Webhooks Guide for the full event payload and retry semantics.
| Need | Source |
|---|---|
| Should we mount the SDK widget? | useTaxbit().needsCuringDocumentation |
| Which specific issues are open? | statusData.wFormQuestionnaire.issues.filter(i => i.status === 'OPEN') |
| Which issues are awaiting review? | …filter(i => i.status === 'IN_REVIEW') |
| Which issues have been resolved? | …filter(i => i.status === 'RESOLVED') |
| Refresh after a filer submits | await useTaxbit().refresh() |
| Same data outside the browser | GET /tax-documentation-status server-to-server |
| React to state changes without polling | ACCOUNT_OWNER_TAX_DOCUMENTATION_STATUS webhook |
OPEN→IN_REVIEWhappens when a filer submits a cure through the SDK.IN_REVIEW→RESOLVEDhappens when your reviewer accepts the cure.IN_REVIEW→OPENhappens when your reviewer rejects the cure or requests additional documentation. The filer can submit again.RESOLVEDis terminal. The issue will not change status again.
A single documentation submission can have multiple issues, each tracked independently. Filter for status === 'OPEN' to know what's actionable at any given moment.

