Monichek API
AML/CFT screening, transaction monitoring, registry lookups and hosted verification for Paysorta's internal products. This site is the contract and the runbook for teams wiring a service into the engine.
There is no public signup, pricing, wallet or billing anywhere in this API. Usage pages are diagnostic telemetry only.
Overview
The API is a company-scoped REST surface. A credential belongs to exactly one company; every request made with it reads and writes that company's rows. Staff sessions can be assigned to several companies and bind one at a time from the console.
Screen a person or business
Sanctions, PEP and watchlist checks with a deterministic, explainable decision.
Run the first call →Monitor transactions
Evaluate payments against rules and risk context, then hold, release or reject.
Browse endpoints →Receive events
Signed webhooks for screenings, decisions, alerts, cases and verifications.
Set up webhooks →Verify a customer
Create a single-use session and hand the customer a hosted verification link.
Verification sessions →Quickstart
Issue a sandbox key in the console under Platform → Developer tools → API keys. Keys are shown once. Store the secret in the integrating service's secret store, never in source control.
Shellexport MONICHEK_API_KEY="wk_test_..."
export MONICHEK_API="https://sandbox-api-monichek.paysorta.com/v1"
curl -s "$MONICHEK_API/me" \
-H "Authorization: Bearer $MONICHEK_API_KEY"
A successful identity call returns the company the credential is pinned to. If it returns
401, the key is revoked, mistyped or from a different environment.
curl -s -X POST "$MONICHEK_API/screenings" \
-H "Authorization: Bearer $MONICHEK_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: onboarding-ada-0001" \
-d '{
"profile": "onboarding_individual",
"subject": {
"type": "person",
"names": [{"full": "Ada Okafor"}],
"date_of_birth": "1990-04-12",
"nationalities": ["NG"]
}
}'
The response carries the decision, score, hits, snapshot and profile version. Treat the decision as the engine's output and the hits as the evidence behind it — both are audited.
Sandbox and live are separate consoles, APIs and keys. A sandbox key cannot call the live host.
Authentication
Send the key as a bearer token. The API resolves the key to a company and applies that company's row-level scope before the controller runs.
Authorization: Bearer wk_live_...
Content-Type: application/json
The API is versioned in the URL path (/v1); there is no version
header to send. Responses include a Monichek-Version header for
traceability, which you can log but do not need to echo back.
Access model
Every key has company-wide data-plane access: screenings, transactions, customers, cases, reports and verification. That is the default and there are no per-service scopes to configure.
A key may additionally carry the Management access flag. That lets it create, rotate and revoke other keys and manage team access. Use it only for an operator or a tightly controlled admin service.
A data-plane key can reach every data-plane endpoint for its company. Rotate on suspected exposure; rotation issues a new secret and leaves the old key valid until you revoke it.
Company header
API-key traffic ignores X-Monichek-Company; the key's company is fixed. Staff
sessions use that header to bind one company at a time. A forged value fails closed rather
than widening scope.
Ed25519 credentials
Services can register an Ed25519 public key instead of a shared secret and sign requests
per the RFC 9421 profile. The private key never leaves the service. See
docs/security/http-signatures.md before choosing this scheme.
Environments
| Environment | Console | API base |
|---|---|---|
| Sandbox | sandbox-monichek.paysorta.com | https://sandbox-api-monichek.paysorta.com/v1 |
| Live | monichek.paysorta.com | https://api-monichek.paysorta.com/v1 |
| Local | — | http://localhost:28080/v1 |
The live and sandbox consoles share one image; only configuration differs. The badge in the sidebar tells you which one you are in.
Errors
Failures use application/problem+json with a stable machine code, a human
detail string and the HTTP status. Branch on code, not on prose.
{
"type": "https://docs-monichek.paysorta.com/errors/auth.forbidden",
"title": "auth.forbidden",
"status": 403,
"code": "auth.forbidden",
"detail": "requires super_admin role"
}
| Code | Status | What to do |
|---|---|---|
auth.unauthenticated | 401 | Missing, expired or revoked credential. Re-issue the key or refresh the session. |
auth.forbidden | 403 | The credential or member role is not allowed to perform this operation. |
company.forbidden | 403 | The requested company is outside the staff member's assignments. |
request.validation_failed | 422 | A field is missing or invalid. The detail names the field where possible. |
webhook.url_denied | 422 | The endpoint is not a permitted public destination (private, loopback or blocked egress). |
webhook.secret_missing | 409 | The endpoint has no signing secret. Rotate it before testing or redelivering. |
request.upstream_unreachable | 502 | The console cannot reach the API. Check the service, not the request payload. |
Retry 429, 502, 503 and 504 with bounded exponential backoff and jitter. Do not retry a 422 unchanged; fix the request first.
Idempotency
Creation endpoints that support it accept an Idempotency-Key header. Use a
stable value derived from your own work item, not a random value on every retry — the
point is to make a repeated delivery safe.
Idempotency-Key: onboarding-ada-0001
Supported today on screening creation, transaction decisions and hosted verification session creation. A replay returns the original response instead of creating a second record. If you do not have a stable key, treat the network error as unknown and query the resource before retrying.
Webhooks
Add a publicly resolvable endpoint in Platform → Developer tools → Webhooks. The signing secret is shown once. An endpoint with an empty event filter receives every event, including events added after the endpoint was created.
POST /v1/webhook-endpoints
{
"url": "https://service.example/monichek/webhook",
"events": ["screening.completed", "transaction.evaluated", "verify.completed"]
}
Events
| Event | Raised when |
|---|---|
screening.completed | A screening finishes, with its decision. |
screening.match_found | A screening returns at least one match. |
screening.batch.completed | A batch screening run finishes. |
transaction.evaluated | A payment is evaluated by the rules engine. |
transaction.held / transaction.released / transaction.rejected | A payment changes hold state or is decided. |
decision.taken | A decision is recorded for an item. |
alert.created / case.created | An alert or case is opened. |
risk.changed | A customer risk rating changes. |
kyb.completed | A business check finishes. |
verify.completed | A hosted verification session reaches a terminal state. |
watch.hit | A watch subscription finds a new list hit. |
monitoring.sweep_completed | A monitoring sweep finishes. |
security.anomaly | A security control records a burst or denial pattern. |
Envelope and signature
Every delivery is a JSON envelope with a stable event id, type and data object.
{
"id": "evt_01M3R...",
"type": "screening.completed",
"tenant_id": "ten_...",
"data": { "screening_id": "scr_...", "decision": "clear" }
}
Headers include:
Monichek-Event: screening.completed
Monichek-Delivery: whd_01M3R...
Monichek-Signature: t=1760000000,v1=<hex hmac-sha256>
The signed bytes are the Unix timestamp, a literal dot, then the raw request body:
t + "." + rawBody. Verify with the endpoint's current signing secret and
compare in constant time.
import crypto from "node:crypto";
function verifyMonichek(rawBody, header, secret) {
const parts = Object.fromEntries(
header.split(",").map((part) => part.trim().split("="))
);
const timestamp = Number(parts.t);
if (!Number.isFinite(timestamp) || Math.abs(Date.now() / 1000 - timestamp) > 300) {
return false;
}
const expected = crypto
.createHmac("sha256", secret)
.update(`${parts.t}.${rawBody}`)
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(expected, "utf8"),
Buffer.from(parts.v1 ?? "", "utf8")
);
}
Retries and replay
- Delivery is asynchronous; the API returns before the receiver is called.
- Transient failures retry with bounded exponential backoff (default up to 8 attempts).
- Permanent 3xx/4xx responses stop early, except
408and429. - Failed deliveries stay visible in the console and can be requeued by hand.
- Rotating the secret re-signs queued deliveries; the old secret stops immediately.
Accept the event quickly, persist it, return 2xx, then process asynchronously. Do not do slow downstream work inside the webhook request.
Verification sessions
Create a single-use session and send the customer the returned hosted_url.
The session tracks identity, liveness and optional address steps; configuration can
brand the hosted page and choose which steps are required.
POST /v1/verify/sessions
Idempotency-Key: onboarding-ada-0001
{
"customer_id": "cus_01M3R...",
"config": {
"brand_name": "Paysorta",
"liveness_required": true,
"address_required": false
},
"redirect_url": "https://app.example/onboarding/return",
"webhook_url": "https://service.example/monichek/verify"
}
The response contains session_id, a short-lived token and
hosted_url. The token is for the hosted flow; keep it out of logs. Read the
session with an API key or the scoped X-Verify-Token header.
Internal operators can review sessions, copy links and inspect outcomes under Monitoring → Verifications.
Console, logs and health
The console is the operational surface for in-house engineers. Everything developer-facing lives under Platform.
| Page | Use it for |
|---|---|
| Platform → Overview | Integration readiness: active keys, webhook endpoint, recent traffic and failing deliveries. |
| Developer tools → API keys | Issue, rotate and revoke company-bound keys; grant management access only when needed. |
| Developer tools → Webhooks | Endpoints, event filters, signing secrets, tests, delivery attempts and redelivery. |
| Developer tools → API logs | Raw request tail: method, path, status, latency, key and remote address. |
| Developer tools → Usage & health | Aggregates over the request tail: volume, error classes, p95 and traffic by key. |
Request logs are retained for 7 days. Usage is for debugging and adoption, not invoicing or quota enforcement.
Changelog
2026-10-23
- Published this documentation site at
docs-monichek.paysorta.com. - Added Platform overview, webhook management and usage & health screens.
- Clarified API-key access: company-wide data plane;
adminis the only enforced scope flag. - Versioning is in the URL path (
/v1); removed the optionalMonichek-Versionrequest header from the examples and SDKs. Responses still report the served version. - API reference is now a static, searchable site generated from the OpenAPI contract.
2026-09-24
- API v1 released.