Valyd Verification MCP

Integration guide for the Model Context Protocol (MCP) server. Connect your AI agent to Valyd approval and verification over streamable HTTP.

Transport: Streamable HTTP · Authentication: Custom headers (X-MCP-*). No OAuth. You configure your MCP client to use the hosted endpoint; you do not run the server yourself.

Base URL & endpoint

Configure your MCP client with the following endpoint. Use no trailing slash.

https://mcp.pollus.tech/verification/mcp

Production base URL: https://mcp.pollus.tech. For local development, use the URL provided by your host.

Overview

Receiving the result: Use webhook and/or polling. Valyd sends the outcome to your webhook when the approver has responded. You can also poll verification_status until status is APPROVED or REJECTED (i.e. no longer PENDING).

Add to your agent

Add the Valyd MCP server to your agent (e.g. Cursor, Claude Desktop, or any MCP client). Use the structure below; set url to your MCP endpoint and the header values to your Valyd credentials.

Example — Cursor: .cursor/mcp.json or Settings → Tools & MCP.

{
  "mcpServers": {
    "valyd-verification": {
      "url": "https://mcp.pollus.tech/verification/mcp",
      "headers": {
        "X-MCP-Client-Id": "YOUR_VALYD_CLIENT_ID",
        "X-MCP-Client-Secret": "YOUR_VALYD_CLIENT_SECRET",
        "X-MCP-Webhook-Url": "https://your-domain.com/api/webhook/valyd"
      }
    }
  }
}

Replace YOUR_VALYD_CLIENT_ID and YOUR_VALYD_CLIENT_SECRET with the credentials from your Valyd (Laravel) admin. Set X-MCP-Webhook-Url to the exact URL registered for this client in Valyd (including or excluding trailing slash so it matches).

Note: Use no trailing slash on url. Restart the agent after changing the configuration.

Setup reference

SettingRequiredDescription
URLYesFull MCP endpoint URL (e.g. https://mcp.pollus.tech/verification/mcp; no trailing slash)
X-MCP-Client-IdYesValyd client ID
X-MCP-Client-SecretYesValyd client secret
X-MCP-Webhook-UrlYesWebhook URL registered in Valyd (Valyd POSTs decisions here). Must match the URL in Valyd admin exactly (including trailing slash).

Set the three headers in your client (e.g. Cursor → Custom Headers, MCP Inspector → Custom Headers). The server does not read credentials from environment variables.

Tools

verification_request

Creates an approval challenge. The server forwards the request to the Valyd API and returns Valyd’s response as-is.

ParameterTypeDescription
action_typestringe.g. "delete", "update", "file_edit_approval"
user_idstringValyd user ID (sent as X-Valyd-User-Id)
titlestringShort title for the action
descriptionstringContext for the approver

Response (JSON) — challenge created, status is PENDING until approver responds

{
  "challenge_id": "uuid-string",
  "status": "PENDING",
  "expires_at": "2026-03-03T09:10:46+00:00",
  "poll_after_ms": 1500,
  "action_digest": "sha256:..."
}

Store challenge_id for status checks or to match webhook events. idempotency_key is generated by the server; do not send it.

On error the tool returns {"status": "error", "message": "..."}. Check message for Valyd error codes (e.g. INVALID_MCP_WEBHOOK_URL, INVALID_CLIENT_ID) or network errors.

verification_status

Returns the current status of a challenge (for polling). The server calls the Valyd API and returns Valyd’s response as-is.

Challenge status — what the values mean

PENDING = the challenge is still waiting for the approver. No outcome yet.

APPROVED = the approver allowed the action. REJECTED = the approver denied it. Once you see either of these, the challenge is resolved (no longer pending). The field decided_at is the timestamp when that outcome was set — it does not mean “approved”; it means “when the final outcome (approved or rejected) was recorded.”

ParameterTypeDescription
challenge_idstringFrom verification_request
user_idstringSame as in verification_request

Response while still waiting (status = PENDING)

{
  "challenge_id": "uuid-string",
  "status": "PENDING",
  "expires_at": "2026-03-03T09:10:46+00:00",
  "poll_after_ms": 1500,
  "action_digest": "sha256:..."
}

Response when challenge is resolved — outcome is APPROVED or REJECTED

{
  "challenge_id": "uuid-string",
  "status": "APPROVED",
  "expires_at": "2026-03-03T09:10:46+00:00",
  "decided_at": "2026-03-03T09:06:00+00:00",
  "action_digest": "sha256:...",
  "decision": {
    "method": "FACE_VERIFICATION",
    "valyd_session_id": "uuid-string",
    "assurance_level": "high"
  }
}

When status is APPROVED or REJECTED, the challenge is resolved. Use the decision object when present for method and assurance details.

On error the tool returns {"status": "error", "message": "..."} (e.g. invalid challenge_id, missing headers, or Valyd API errors).

Getting the result: webhook or polling

Webhook: Register your endpoint in Valyd and send that URL in X-MCP-Webhook-Url. When the approver has responded (challenge resolved — approved or rejected), Valyd POSTs to your webhook. You may use only webhook, only polling, or both.

Polling: Call verification_status with the challenge_id and same user_id at an interval (e.g. poll_after_ms). Stop when status is no longer PENDING (i.e. when it is APPROVED or REJECTED).

Webhook contract

When the approver has responded and the challenge is resolved (outcome is APPROVED or REJECTED), Valyd POSTs the event to the URL you registered (the same URL you send in X-MCP-Webhook-Url). If you host this MCP server yourself, the webhook route is POST /webhooks/valyd/approval (full URL = your base URL + /webhooks/valyd/approval). If you use a different backend, implement the same request/response contract below.

The shapes below are the exact contract—no extra fields.

Request body (JSON) Valyd sends to your webhook (when the challenge is resolved):

FieldTypeDescription
versionstringEvent version
event_idstringEvent ID
typestringEvent type
challenge_idstringSame id from verification_request
project_idstringProject ID
statusstringAPPROVED or REJECTED
action_digeststringAction digest
occurred_atstring (ISO 8601)When the outcome was set (approved or rejected)
decisionobjectmethod, valyd_session_id, assurance_level
{
  "version": "1.0",
  "event_id": "evt_xxx",
  "type": "approval.decided",
  "challenge_id": "uuid-string",
  "project_id": "string",
  "status": "APPROVED",
  "action_digest": "sha256:...",
  "occurred_at": "2026-03-03T09:06:00+00:00",
  "decision": {
    "method": "FACE_VERIFICATION",
    "valyd_session_id": "uuid-string",
    "assurance_level": "high"
  }
}

Response body your endpoint must return (200 OK; only these four fields):

{
  "received": true,
  "challenge_id": "uuid-string",
  "status": "APPROVED",
  "occurred_at": "2026-03-03T09:06:00+00:00"
}

Respond with 200 and this JSON so Valyd confirms the event was received. Use challenge_id to match your pending request; use status and decision in your application to continue or abort.

Agent flow

  1. Call verification_request with action_type, user_id, title, description.
  2. Get the outcome via webhook and/or polling (verification_status until status is APPROVED or REJECTED).
  3. If status is APPROVED, perform the action; if REJECTED, abort and inform the user.

Common errors

ErrorResolution
INVALID_MCP_WEBHOOK_URLX-MCP-Webhook-Url must match the URL registered for this client in Valyd exactly (including trailing slash). Update either Valyd admin or your header so they match.
INVALID_CLIENT_IDUse valid Valyd credentials in the three headers (from Valyd admin).
valyd_user_id_requiredSend user_id in both verification_request and verification_status (it is sent as X-Valyd-User-Id to Valyd).

All of these are returned in the tool response as {"status": "error", "message": "..."}.