Alta de contratistas con el formulario W-9
Valida el formulario W-9 con el tipo us_w9 sin guardarlo, obtén un veredicto con firma y titular, y recibe el nombre, la clasificación fiscal, el TIN y la dirección.
Antes de pagar a un contratista (contractor) de EE. UU., recoges un formulario W-9 con su nombre legal, nombre comercial, clasificación fiscal federal, dirección y número de identificación fiscal (TIN: un SSN o un EIN). Leer esos PDF a mano es lento y propenso a errores; guardar copias por ahí es un riesgo.
Esta guía usa el tipo de catálogo us_w9 con expect: Constaia extrae los campos, comprueba el formato del TIN, la
firma y el nombre, y devuelve un verdict, sin guardar nunca el documento. El cotejo del TIN con el IRS sigue siendo
cosa tuya.
El flujo
El contratista sube el W-9 firmado
Tu portal de alta envía el PDF o la foto a tu backend. La clave de API nunca sale de tu servidor.
Tu backend analiza el W-9 sin guardarlo
Llama a POST /v1/analyze con expect: "us_w9", los checks que necesites, storage: "none" y keep_results: false.
La respuesta es el único sitio donde aparece el TIN; en Constaia no queda nada.
Tu código decide y guarda
Según verdict.status (valid, review o invalid) das de alta al contratista, lo mandas a revisión o le pides otro
formulario. Guardas el TIN cifrado en tu sistema y pasas el registro a tu equipo de pagos o al cotejo del TIN.
El tipo us_w9
El tipo us_w9 devuelve estos campos:
| Campo | Contenido |
|---|---|
name | Línea 1: nombre tal como figura en la declaración de impuestos. |
business_name | Línea 2: nombre comercial o de la entidad no considerada (disregarded entity). |
tax_classification | Clasificación fiscal federal (individual, C corp, S corp, partnership, LLC…). |
exempt_payee_code | Código de beneficiario exento, si figura. |
address, postal_code | Dirección y código ZIP. |
ssn, ein | El TIN, en la casilla en la que se escribió (solo una suele tener valor). |
signature_present, signature_date | Si está firmado y la fecha de la firma. |
form_revision | Revisión del formulario, por ejemplo Rev. March 2024. |
Con cada análisis, Constaia valida el formato de ssn (esquema us_ssn), ein (us_ein) y postal_code (us_zip).
El resultado aparece en checks[] con el código id_number_format. Estas validaciones son soft: si fallan, el motivo
llega a verdict.reasons con severidad warning y el veredicto pasa a review, no a invalid.
Los checks que admite el tipo son holder, require_fields, require_signature, max_age_days (sobre
signature_date) y reference_date. La lista completa, siempre al día, está en
GET /v1/document-types/us_w9 y en el catálogo.
Analizar y decidir
Solo formato
id_number_format comprueba la estructura del número (nueve cifras, rangos no asignados como 000, 666 o 9xx
en un SSN, prefijos de EIN válidos). No es una validación contra el IRS ni la Social Security Administration: un TIN
con formato correcto puede seguir siendo erróneo o de otra persona.
import { Constaia, ConstaiaError } from "@constaia/sdk";
import { fromPath } from "@constaia/sdk/node";
const constaia = new Constaia(); // lee CONSTAIA_API_KEY
type Contractor = { id: string; legalName: string; tin?: string }; // lo que escribió en tu formulario de alta
export async function processW9(path: string, contractor: Contractor) {
const analysis = await constaia.analyze(await fromPath(path), {
expect: "us_w9",
checks: {
requireSignature: true,
requireFields: ["name", "tax_classification"],
holder: { fullName: contractor.legalName },
},
storage: "none",
keepResults: false,
metadata: { contractor_id: contractor.id }, // nunca el TIN
});
if (analysis.status !== "completed") return { status: "pending", analysisId: analysis.id };
const v = (name: string) => analysis.fields[name]?.value ?? null;
const tinType = v("ein") ? "ein" : v("ssn") ? "ssn" : null;
const tin = String(v("ein") ?? v("ssn") ?? "").replace(/\D/g, "");
// Motivos que no son informativos: type_mismatch, holder, require_signature, id_number_format…
const issues = (analysis.verdict?.reasons ?? [])
.filter((r) => r.severity !== "info")
.map((r) => `${r.code}:${r.severity}`);
if (contractor.tin && contractor.tin.replace(/\D/g, "") !== tin) issues.push("tin_differs_from_form:error");
const status =
analysis.verdict?.status === "invalid" ? "rejected" : issues.length ? "needs_review" : "ok";
return {
status,
issues,
record: {
name: v("name"),
businessName: v("business_name"),
taxClassification: v("tax_classification"),
exemptPayeeCode: v("exempt_payee_code"),
address: v("address"),
postalCode: v("postal_code"),
tinType,
tin, // guárdalo cifrado; muestra y registra solo los 4 últimos
tinLast4: tin.slice(-4),
signatureDate: v("signature_date"),
formRevision: v("form_revision"),
},
};
}
processW9(process.argv[2] ?? "./w9.pdf", { id: "ctr_42", legalName: "Specimen Consulting LLC" })
.then(({ status, issues, record }) => console.log({ status, issues, tinLast4: record?.tinLast4 }))
.catch((err) => {
if (err instanceof ConstaiaError) console.error(`Constaia error ${err.status} ${err.code} (request ${err.requestId})`);
else console.error(err);
process.exit(1);
});Un W-9 con un SSN mal formado y todo lo demás correcto vuelve así (con language: "es"):
{
"expected": ["us_w9"],
"match": true,
"status": "review",
"reasons": [
{ "code": "type_match", "severity": "info", "message": "El documento es Formulario W-9 (EE. UU.)." },
{ "code": "holder", "severity": "info", "message": "Los datos del titular coinciden (full_name)." },
{ "code": "require_signature", "severity": "info", "message": "El documento está firmado." },
{ "code": "id_number_format", "severity": "warning", "message": "Validación fallida: ssn (us_ssn) no tiene un formato válido: 666123456." }
]
}Qué significa cada resultado:
valid: es un W-9, está firmado, el nombre coincide y el TIN tiene un formato correcto.review: algo merece una mirada: un TIN con formato inválido, un campo que no se pudo leer, baja calidad de imagen o baja confianza. Envíalo a revisión humana.invalid: no es un W-9 (type_mismatch, por ejemplo un W-8BEN), falta la firma, falta un campo obligatorio o el nombre no coincide. Pide al contratista un formulario nuevo.
ITIN en la casilla del SSN
El campo ssn se valida con el esquema us_ssn, que rechaza los números que empiezan por 9. Un ITIN escrito en la
casilla del SSN, algo habitual y correcto en un W-9, da por eso un aviso id_number_format y el veredicto review.
Si aceptas ITIN, compruébalo en tu código (nueve cifras que empiezan por 9) antes de mandar el formulario a revisión.
Un W-9 completo con sus instrucciones tiene 6 páginas y cuesta 3 créditos (1 crédito hasta 2 páginas, +1 por cada 2 páginas más). Si pides solo la primera página, cuesta 1. Consulta Créditos y facturación.
Contratistas con EIN: la carta del IRS
Si el contratista es una empresa, puedes pedir también la carta en la que el IRS le asignó el EIN (CP 575) o se lo
confirmó (147C). El tipo us_irs_ein_letter extrae business_name, ein, address, postal_code, notice_type e
issue_date. En este tipo la validación del EIN no es soft: un EIN con formato inválido da invalid.
Para cruzarla con el W-9, manda el nombre y el EIN leídos del W-9 en holder:
// w9: el registro (record) que devuelve processW9
const letter = await constaia.analyze(await fromPath("./cp575.pdf"), {
expect: "us_irs_ein_letter",
checks: { holder: { fullName: w9.businessName ?? w9.name, documentNumber: w9.tin } },
storage: "none",
keepResults: false,
});
console.log(letter.verdict?.status); // "valid" si nombre y EIN coinciden con el W-9holder.document_number se compara sin espacios ni guiones. Que la carta coincida con el W-9 no sustituye al cotejo con
el IRS: ambos documentos los aporta el propio contratista.
Tratamiento del TIN
- Trátalo como dato muy sensible. Mantén
storage: "none"ykeep_results: false, así Constaia no guarda ni el fichero ni los campos extraídos (solo los metadatos de facturación). No pongas nunca el TIN enmetadata, logs ni mensajes de error; muestra solo los cuatro últimos dígitos. - Cífralo en reposo en tu sistema y limita quién puede leerlo.
- Cotéjalo con el IRS tú mismo. Constaia valida el formato de SSN y EIN, pero no consulta al IRS (no usa el programa TIN Matching) ni a la Social Security Administration ni ninguna otra base de datos pública. Si necesitas confirmar que la combinación de nombre y TIN es correcta, usa TIN Matching si cumples los requisitos, o el servicio de tu proveedor de pagos.
- Firma.
require_signaturecomprueba que hay una firma visible en la casilla Sign Here; no verifica quién firmó.
Constaia actúa como proveedor de servicios (service provider) según la CCPA y dentro de tu programa de seguridad de la GLBA si te aplica. Consulta con tu asesoría jurídica o fiscal la retención de respaldo (backup withholding), la conservación de registros y los requisitos del W-9 electrónico; esta guía no es asesoramiento jurídico.
Modo test
El simulador del modo test (claves ck_test_) no tiene escenarios de documentos de EE. UU. Un W-9 se responde con el
escenario generic, así que con expect: "us_w9" el veredicto es review con el motivo type_unknown (los nombres
de fichero que contienen invoice o receipt se responden con escenarios de factura y justificante de pago
españoles). Úsalo para probar tu integración y tus rutas de error; para ver resultados reales de un W-9 usa una clave
live. El plan gratuito incluye 150 créditos al mes; los créditos live gratuitos requieren un email verificado.
Consulta Modo test.
Próximamente: región de EE. UU.
Hoy todo el procesamiento y el almacenamiento, también el de clientes de EE. UU., se hace en la UE. Está prevista una región de EE. UU., sin fecha todavía. Consulta Residencia de datos y cumplimiento.
Siguientes pasos
Documentos de EE. UU.
Todos los tipos de EE. UU. del catálogo y qué comprueba cada uno.
Analizar sin guardar
La configuración sin retención, en detalle.
Documentos del formulario I-9
Prerrellena la sección 2 con los documentos del nuevo empleado.
Revisión humana
Envía a una persona los formularios con incidencias.
Comprobaciones
holder, require_signature, require_fields y el resto.
Documentos del formulario I-9
Verifica los documentos de las listas A, B y C que presenta un nuevo empleado para el formulario I-9, etiquétalos por lista y prerrellena la sección 2 para RR. HH., con Node o Python.
Verificación de ingresos con nóminas y extractos bancarios
Analiza en un lote nóminas y extractos bancarios de EE. UU. con los tipos us_paystub y us_bank_statement, con antigüedad y titular comprobados, y calcula el ingreso mensual en tu código.