Valyd Verification MCP
Integration guide for the Model Context Protocol (MCP) server. Connect your AI agent to Valyd approval and verification over streamable HTTP.
Base URL & endpoint
Configure your MCP client with the following endpoint. Use no trailing slash.
Production base URL: https://mcp.pollus.tech. For local development, use the URL provided by your host.
Overview
- Transport: Streamable HTTP
- Authentication: Send
X-MCP-Client-Id,X-MCP-Client-Secret, andX-MCP-Webhook-Urlon every request. The server addsX-MCP-TimestampandX-MCP-Signature. OAuth is not used.
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).
- Cursor: Place this in your project’s
.cursor/mcp.jsonor add the server under Settings → Tools & MCP with the sameurland headers. - Other agents: Merge the
valyd-verificationentry into your agent’s MCP configuration (e.g.mcpServersin Claude Desktop).
Note: Use no trailing slash on url. Restart the agent after changing the configuration.
Setup reference
| Setting | Required | Description |
|---|---|---|
| URL | Yes | Full MCP endpoint URL (e.g. https://mcp.pollus.tech/verification/mcp; no trailing slash) |
X-MCP-Client-Id | Yes | Valyd client ID |
X-MCP-Client-Secret | Yes | Valyd client secret |
X-MCP-Webhook-Url | Yes | Webhook 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.
| Parameter | Type | Description |
|---|---|---|
action_type | string | e.g. "delete", "update", "file_edit_approval" |
user_id | string | Valyd user ID (sent as X-Valyd-User-Id) |
title | string | Short title for the action |
description | string | Context 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.”
| Parameter | Type | Description |
|---|---|---|
challenge_id | string | From verification_request |
user_id | string | Same 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):
| Field | Type | Description |
|---|---|---|
version | string | Event version |
event_id | string | Event ID |
type | string | Event type |
challenge_id | string | Same id from verification_request |
project_id | string | Project ID |
status | string | APPROVED or REJECTED |
action_digest | string | Action digest |
occurred_at | string (ISO 8601) | When the outcome was set (approved or rejected) |
decision | object | method, 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
- Call
verification_requestwithaction_type,user_id,title,description. - Get the outcome via webhook and/or polling (
verification_statusuntilstatusisAPPROVEDorREJECTED). - If
statusisAPPROVED, perform the action; ifREJECTED, abort and inform the user.
Common errors
| Error | Resolution |
|---|---|
INVALID_MCP_WEBHOOK_URL | X-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_ID | Use valid Valyd credentials in the three headers (from Valyd admin). |
valyd_user_id_required | Send 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": "..."}.