VerifyMX Docs

Web SDK

Drop-in verification widget for browsers via CDN and npm. (Coming soon.)

⚠️ Próximamente — not yet released. This document describes the intended shape of the embeddable VerifyMX Web SDK / widget. Neither the npm package nor the CDN URL referenced below are live yet. For now, integrate via the REST API directly (see README.md) — your backend creates a verification, then redirects the user to the hosted capture flow at verify.verifymx.xyz (the buyer portal). We'll announce when the embeddable widget ships.

The VerifyMX Web SDK will provide a drop-in verification widget that handles camera capture, document photography, liveness detection, and OTP confirmation entirely in the browser. Your backend stays clean — it only receives the final webhook event.


Installation (when released)

CDN (script tag)

Add this script to your page, ideally in <head> with defer:

<script defer src="https://cdn.verifymx.xyz/sdk/v1/verifymx.min.js"></script>

npm

npm install @verifymx/web-sdk
# or
pnpm add @verifymx/web-sdk

Quick Start — Button Embed

The simplest integration: a button that opens the verification modal.

<!DOCTYPE html>
<html lang="es">
<head>
  <meta charset="UTF-8">
  <script defer src="https://cdn.verifymx.xyz/sdk/v1/verifymx.min.js"></script>
</head>
<body>
  <button id="verify-btn">Verificar identidad</button>

  <script>
    document.getElementById('verify-btn').addEventListener('click', async () => {
      // 1. Your backend creates a verification and returns the ID
      const res = await fetch('/api/create-verification', { method: 'POST' });
      const { verificationId } = await res.json();

      // 2. Open the VerifyMX widget
      const vmx = new VerifyMX({
        verificationId,
        onComplete: (result) => {
          console.log('Verification complete:', result.status);
          // Redirect or update UI
        },
        onError: (error) => {
          console.error('SDK error:', error.code, error.message);
        },
        onExit: () => {
          console.log('User closed the widget without completing');
        },
      });

      vmx.open();
    });
  </script>
</body>
</html>

Your backend route (/api/create-verification) example in Node.js:

app.post('/api/create-verification', async (req, res) => {
  const token = await getVerifyMXToken(); // cache this, expires in 1h

  const response = await fetch('https://api.verifymx.xyz/v1/verifications', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${token}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      workflowId: process.env.VERIFYMX_WORKFLOW_ID,
      externalId: req.user.id, // link to your user
    }),
  });

  const { verification } = await response.json();
  res.json({ verificationId: verification.id });
});

Configuration Options

const vmx = new VerifyMX({
  // REQUIRED
  verificationId: 'ver_01HXYZ...',

  // CALLBACKS
  onComplete: (result) => {},   // called when all steps finish
  onError:    (error)  => {},   // called on unrecoverable error
  onExit:     ()       => {},   // called when user closes modal

  // OPTIONAL
  language:     'es',           // 'es' (default) or 'en'
  theme:        'light',        // 'light' (default) or 'dark'
  primaryColor: '#0057FF',      // hex, overrides button/accent color
  logoUrl:      'https://myapp.mx/logo.png', // shown in widget header

  // FLOW CONTROL
  allowRetry:   true,           // show retry button on failure (default true)
  showProgress: true,           // show step progress bar (default true)

  // CAMERA
  facingMode:   'environment',  // 'environment' (back camera, default) or 'user'
  cameraResolution: { width: 1280, height: 720 },

  // CONTAINER (for embedded mode — omit for modal)
  container:    '#verify-container',
});

Events

onComplete(result)

Called when all workflow steps finish. result shape:

{
  verificationId: 'ver_01HXYZ...',
  status: 'APPROVED' | 'REJECTED' | 'MANUAL_REVIEW',
  steps: [
    { type: 'DOCUMENT_OCR', status: 'PASSED' },
    { type: 'FACE_MATCH',   status: 'PASSED' },
  ]
}

Do not trust the client-side status for access control decisions — always confirm via server-side webhook or API call (GET /v1/verifications/:id).

onError(error)

{
  code: 'CAMERA_PERMISSION_DENIED' | 'NETWORK_ERROR' | 'SESSION_EXPIRED' | 'UNKNOWN',
  message: 'Human-readable description'
}

Common error codes:

CodeCause
CAMERA_PERMISSION_DENIEDUser denied camera access
NETWORK_ERRORAPI unreachable
SESSION_EXPIREDverificationId is no longer valid
MAX_RETRIES_EXCEEDEDToo many failed capture attempts

onExit()

Called when the user closes the modal or clicks "Cancel" before completing.


Camera Capture API

The SDK exposes a headless capture API if you want to build a fully custom UI:

import { VerifyMXCapture } from '@verifymx/web-sdk';

const capture = new VerifyMXCapture({
  verificationId: 'ver_01HXYZ...',
  videoElement: document.getElementById('camera-preview'),
});

await capture.start(); // requests camera permission and starts preview

// Capture document front
const frontDoc = await capture.captureDocument('front');
// { blob: Blob, mimeType: 'image/jpeg' }

// Capture selfie
const selfie = await capture.captureSelfie();

// Upload to API (or let the SDK handle it)
await capture.upload([
  { fieldname: 'front',  blob: frontDoc.blob,  mimeType: frontDoc.mimeType },
  { fieldname: 'selfie', blob: selfie.blob,    mimeType: selfie.mimeType  },
]);

capture.stop(); // releases camera

Instance Methods

const vmx = new VerifyMX({ verificationId: '...' });

vmx.open();   // open the modal (or mount to container)
vmx.close();  // programmatically close/unmount
vmx.destroy(); // remove DOM nodes and release camera

Customization (CSS)

The widget exposes CSS custom properties for theming. Set them on :root or the widget container before calling vmx.open():

:root {
  --vmx-primary:        #0057FF;  /* primary button and accent color */
  --vmx-primary-hover:  #0041CC;
  --vmx-bg:             #FFFFFF;  /* modal background */
  --vmx-surface:        #F5F5F5;  /* step card background */
  --vmx-text:           #111111;
  --vmx-text-secondary: #666666;
  --vmx-border-radius:  12px;     /* button and card corner radius */
  --vmx-font-family:    'Inter', sans-serif;
}

Framework Integration

React

import { useEffect, useRef } from 'react';
import { VerifyMX } from '@verifymx/web-sdk';

interface Props {
  verificationId: string;
  onComplete: (result: VerifyMXResult) => void;
}

export function VerifyButton({ verificationId, onComplete }: Props) {
  const vmxRef = useRef<VerifyMX | null>(null);

  useEffect(() => {
    vmxRef.current = new VerifyMX({
      verificationId,
      onComplete,
      onError: (err) => console.error(err),
      onExit:  () => console.log('User exited'),
    });

    return () => {
      vmxRef.current?.destroy();
    };
  }, [verificationId]);

  return (
    <button onClick={() => vmxRef.current?.open()}>
      Verificar identidad
    </button>
  );
}

Vue 3

<template>
  <button @click="openWidget">Verificar identidad</button>
</template>

<script setup lang="ts">
import { onMounted, onUnmounted, ref } from 'vue';
import { VerifyMX } from '@verifymx/web-sdk';

const props = defineProps<{ verificationId: string }>();
const emit  = defineEmits(['complete']);

let vmx: VerifyMX | null = null;

onMounted(() => {
  vmx = new VerifyMX({
    verificationId: props.verificationId,
    onComplete: (result) => emit('complete', result),
    onError:    (err)    => console.error(err),
  });
});

onUnmounted(() => vmx?.destroy());

const openWidget = () => vmx?.open();
</script>

Angular

import { Component, Input, OnDestroy, OnInit } from '@angular/core';
import { VerifyMX } from '@verifymx/web-sdk';

@Component({
  selector: 'app-verify-button',
  template: '<button (click)="open()">Verificar identidad</button>',
})
export class VerifyButtonComponent implements OnInit, OnDestroy {
  @Input() verificationId!: string;
  private vmx!: VerifyMX;

  ngOnInit() {
    this.vmx = new VerifyMX({
      verificationId: this.verificationId,
      onComplete: (r) => console.log(r.status),
      onError:    (e) => console.error(e),
    });
  }

  open() { this.vmx.open(); }

  ngOnDestroy() { this.vmx.destroy(); }
}

Browser Compatibility

BrowserMinimum version
Chrome90+
Firefox88+
Safari14.1+
Edge90+
Chrome Android90+
Safari iOS14.5+

The widget requires getUserMedia (camera access). HTTPS is mandatory in production — most browsers block getUserMedia on insecure origins.


Security Notes

  • Never embed your raw API key or bearer token in client-side code. Create the verification server-side and pass only the verificationId to the browser.
  • The verificationId grants access to upload documents for that single session only. It cannot list other verifications or access org settings.
  • Final approval decisions should always be read from a server-side webhook or GET /v1/verifications/:id call — never trust the client-side onComplete result alone for access control.

On this page