VerifyMX Docs

Node.js SDK

Official typed Node.js / TypeScript wrapper for VerifyMX. Handles auth, retries, idempotency, webhook signatures and polling so you don't have to.

The official Node.js SDK is a thin, typed wrapper around the REST API. It handles OAuth token caching + refresh, retries on rate-limit and 5xx, idempotency-key generation, multipart uploads, HMAC webhook signature verification, and a waitUntilTerminal() polling helper.

Status: 0.1.0-beta — API surface is stable, minor pre-1.0 changes still possible. See the changelog. Requires Node 18+ (uses native fetch and FormData).

Install

npm install @verifymx/sdk-node
# or
pnpm add @verifymx/sdk-node
# or
yarn add @verifymx/sdk-node

First call

import { VerifyMXClient } from '@verifymx/sdk-node';
import { readFileSync } from 'node:fs';

const vmx = new VerifyMXClient({
  clientId: process.env.VERIFYMX_CLIENT_ID!,
  clientSecret: process.env.VERIFYMX_CLIENT_SECRET!,
});

// 1. Create a verification
const ver = await vmx.createVerification({ workflowId: 'wf_default' });

// 2. Upload the user's INE front photo
await vmx.uploadDocument(
  ver.id,
  readFileSync('./ine-front.jpg'),
  'ine-front.jpg',
  'front',
);

// 3. Wait for the verdict
const result = await vmx.waitUntilTerminal(ver.id);
// result.status: 'APPROVED' | 'REJECTED' | 'MANUAL_REVIEW' | 'EXPIRED'

That's the full happy path. ~5 lines replaces ~80 of raw fetch + auth + retry + idempotency boilerplate.

Configuration

new VerifyMXClient({
  clientId: '...',                    // required
  clientSecret: '...',                // required
  apiUrl: 'https://api.verifymx.xyz', // default; override for staging
  timeoutMs: 30_000,                  // per-request timeout
  retries: {
    max: 3,                           // max retries after the first try
    baseDelayMs: 500,                 // doubles per attempt, capped at 16s
  },
  userAgent: 'my-app/1.4.2',          // optional, appended to the SDK UA
});

Typed error handling

Every error thrown by the SDK extends VerifyMXError. HTTP-class subclasses let you branch with instanceof:

import {
  RateLimitedError,
  UnauthorizedError,
  ValidationError,
  VerifyMXError,
} from '@verifymx/sdk-node';

try {
  await vmx.createVerification({ workflowId: 'wf_default' });
} catch (err) {
  if (err instanceof RateLimitedError) {
    // SDK exhausted retries. Caller is over budget.
    console.warn(`Wait ${err.retryAfter}s`);
  } else if (err instanceof UnauthorizedError) {
    console.error('Bad credentials.');
  } else if (err instanceof ValidationError) {
    console.error('Bad input:', err.details);
  } else if (err instanceof VerifyMXError) {
    console.error(`code=${err.code} requestId=${err.requestId}`);
  } else {
    throw err;
  }
}

The code field on VerifyMXError mirrors the server's stable taxonomy ('WORKFLOW_INACTIVE', 'CURP_STATUS_ANNULLED', 'VALIDATION_ERROR', ...). Branch on err.code for business-rule failures, on the instanceof hierarchy for HTTP-class buckets.

Webhook signature verification

The SDK ships a constant-time HMAC verifier plus an Express/Fastify- compatible middleware:

import express from 'express';
import { webhookMiddleware } from '@verifymx/sdk-node';

const app = express();

app.post(
  '/webhooks/verifymx',
  express.raw({ type: 'application/json' }),       // raw body required
  webhookMiddleware(process.env.WEBHOOK_SECRET!),
  (req, res) => {
    const event = JSON.parse(req.body.toString('utf-8'));
    // event.type === 'verification.approved' | ...
    res.status(200).send('ok');
  },
);

Important: always pass the raw request body to the verifier. If you pass parsed JSON re-serialised, the HMAC will mismatch on whitespace and key ordering. See the Webhooks page for the full signature scheme.

What the SDK takes care of for you

  • OAuth client_credentials with a cached token and refresh 30 s before expiry.
  • Retry with backoff on HTTP 429 (honors Retry-After), 5xx, and transient network errors. Default: 3 retries, exponential backoff capped at 16 s.
  • Idempotency keys auto-generated on every POST so retries are safe. Override with { idempotencyKey } when you need cross-process dedup.
  • Polling with timeout: waitUntilTerminal().
  • Per-request timeout via AbortSignal.timeout (default 30 s).
  • Native fetch + FormData — no third-party HTTP client.

Method reference

MethodEndpoint
createVerification(input)POST /v1/verifications
getVerification(id)GET /v1/verifications/:id
listVerifications(opts?)GET /v1/verifications
updateVerificationStatus(id, status, notes?)PATCH /v1/verifications/:id/status
uploadDocument(id, buf, filename, type)POST /v1/verifications/:id/inputs
waitUntilTerminal(id, opts?)polling helper
createWorkflow(data) / getWorkflow(id) / listWorkflows() / updateWorkflow(id, data) / deleteWorkflow(id)/v1/workflows
createWebhook(url, events) / listWebhooks() / deleteWebhook(id)/v1/webhooks
screenName(name, opts?) / getAmlLists()/v1/aml
sendOtp(...) / verifyOtp(...)/v1/otp/{send,verify}

Source

On this page