Build a reliable OnComply integration
Use the supported public API surface and signed webhooks to keep vendor eligibility in sync with your systems.
Base URL
https://api.oncomply.biz
Public API
3 supported endpoints
Format
JSON, UTF-8
Timestamps
RFC 3339, UTC
API keys for server-to-server access
An owner or admin can create a key from the API Keys page when public integration access is enabled. Keys begin with ocpk_ and are shown once. Store the value in a secrets manager and never embed it in browser or mobile code.
Send the key as X-API-Key or Authorization: Bearer <api_key>. A missing required scope is returned as 401. Revoking a key takes effect immediately.
curl "https://api.oncomply.biz/api/v1/vendors/{vendor_id}/eligibility" \
-H "X-API-Key: $ONCOMPLY_API_KEY"Supported scopes
eligibility:read- Read one vendor's current eligibility snapshot.
eligibility:read:bulk- Read eligibility snapshots for multiple vendor IDs.
integrations:vendors:write- Resolve, link, or start onboarding a vendor from an external system.
Ask whether a vendor can work and be paid
Eligibility is a live snapshot of the same compliance state shown in OnComply. Use can_work and can_pay as explicit gates in work-order and payment workflows.
Status is pending, compliant, blocked_payment, or blocked_work. Blocking scope is none, audit, payment, or work.
next_expiration_at is the next known date-based expiration. Documents and other events can change the snapshot sooner, so use webhooks when you need prompt updates.
Where to get the vendor ID
vendor_id identifies the OnComply vendor record; it is not a user ID or your external system's vendor ID. For a matched, linked, or created resolution response, store vendor.id alongside your provider and external_id. Vendor-event webhooks include the same value as vendor_id. For vendors created in OnComply, open the vendor record in the admin app and use Copy ID beside the OnComply Vendor ID.
/api/v1/vendors/{vendor_id}/eligibility{
"status": "compliant",
"can_work": true,
"can_pay": true,
"blocking_scope": "none",
"blocking_reasons": [],
"next_expiration_at": "2026-11-02T00:00:00Z"
}/api/v1/vendors/bulk-eligibilitycurl -X POST "https://api.oncomply.biz/api/v1/vendors/bulk-eligibility" \
-H "X-API-Key: $ONCOMPLY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"vendor_ids":["8f14e45f-ceea-4f31-9c70-6f1f2a2b9e01","6512bd43-d9ca-4e58-8b6f-4d1b5c7a2f88"]}'{
"items": [
{
"vendor_id": "8f14e45f-ceea-4f31-9c70-6f1f2a2b9e01",
"status": "compliant",
"can_work": true,
"can_pay": true,
"blocking_scope": "none",
"blocking_reasons": [],
"next_expiration_at": "2026-11-02T00:00:00Z"
},
{
"vendor_id": "6512bd43-d9ca-4e58-8b6f-4d1b5c7a2f88",
"status": "blocked_payment",
"can_work": true,
"can_pay": false,
"blocking_scope": "payment",
"blocking_reasons": [
{
"requirement_id": "0a1b2c3d-4e5f-6a7b-8c9d-0e1f2a3b4c5d",
"type": "ach",
"status": "pending",
"scope": "payment"
}
],
"next_expiration_at": null
}
]
}Results preserve the submitted order. Unknown or out-of-workspace vendor IDs are omitted from items.
Sync a vendor from an external system
Resolve uses a provider-qualified external ID as the durable identity. It first reuses an existing external link; when appropriate, it can link a unique existing vendor or create a new vendor and start onboarding.
Every successful match includes the current eligibility snapshot, so the same response can update your local mapping and gate the next action. Use the integrations:vendors:write scope.
/api/v1/integrations/vendors/resolve{
"provider": "erp",
"external_id": "184",
"email": "ap@acmefabrication.com",
"name": "Acme Fabrication",
"legal_name": "Acme Fabrication LLC",
"onboarding_group_id": "d2f0a7c4-9b3e-4f6a-8c1d-5e7b9a0c2d4f",
"track_1099": true,
"start_onboarding": true
}{
"status": "created",
"vendor": {
"id": "3b9d4a6e-1f2c-4d8b-a5e7-9c0f1a2b3c4d",
"email": "ap@acmefabrication.com",
"name": "Acme Fabrication",
"legal_name": "Acme Fabrication LLC",
"onboarding_group_id": "d2f0a7c4-9b3e-4f6a-8c1d-5e7b9a0c2d4f",
"status": "pending",
"can_work": false,
"can_pay": false,
"external_refs": [
{ "provider": "erp", "external_id": "184" }
]
},
"eligibility": {
"status": "pending",
"can_work": false,
"can_pay": false,
"blocking_scope": "work",
"blocking_reasons": ["..."],
"next_expiration_at": null
},
"onboarding_started": true,
"email_queued": true,
"invite_expires_at": "2026-08-11T16:20:00Z"
}Request guidance
- provider
- Required. Normalized to lowercase; use 2–32 characters beginning with a letter or number, followed by letters, numbers, underscores, or hyphens.
- external_id
- Required, with a maximum of 200 characters. Together with provider, this is the durable identity used on future syncs.
- email, name, legal_name
- Optional profile data used to find a unique existing vendor or populate a newly created vendor.
- onboarding_group_id
- Required when an unmatched vendor should be created and onboarding should start.
- start_onboarding
- When true, an unmatched vendor can be created. When false, an unmatched vendor returns 404. Vendors already invited or submitted are not automatically re-invited.
- track_1099
- When false, an unmatched vendor is skipped instead of entering onboarding.
Response outcomes
matched- The external reference already belongs to this vendor.
linked- A unique existing vendor was found and linked to the external reference.
created- A new vendor was created and prepared for onboarding.
skipped- No vendor was created. skipped_reason is track_1099_false when tracking is disabled or vendor_excluded when the vendor is excluded.
If no email is available, a vendor can still be created, but no invitation email is queued. Check email_queued instead of assuming one was sent.
Signed notifications for vendor events
An owner or admin can register an HTTPS receiver from the Webhooks page. OnComply sends a signed JSON request whenever a subscribed event occurs. The signing secret is shown once when the receiver is created; store it in a secrets manager.
Supported vendor events
vendor.compliance_changed- A vendor's eligibility status changes.
- Fields: previous_status, new_status
vendor.invited- An onboarding invitation is sent or resent.
- Fields: invite_expires_at, scope, resend
vendor.onboarding_completed- A vendor submits onboarding for the first time.
- Fields: queued_documents, terms_version
vendor.document.uploaded- A submitted document passes upload scanning and enters processing.
- Fields: document_id, requirement_id, mime_type, file_size
vendor.document.failed_validation- Automated processing cannot finish and the document enters manual review.
- Fields: document_id, requirement_id, reason
vendor.expiring_soon- A passing document or requirement approaches expiration or renewal.
- Fields: requirement_id, document_id, expires_at, due_at, days, due_kind
Vendor-event payloads always include event, timestamp, and vendor_id. A manual webhook.test delivery instead identifies the workspace, receiver, and delivery; it does not include a vendor ID.
Content-Type: application/json
X-OnComply-Event: vendor.compliance_changed
X-OnComply-Delivery: 7f3d2a1b-9c8e-4f5a-b6d7-0e1f2a3b4c5d
X-OnComply-Signature: sha256=a1b2c3d4e5f6...import crypto from "node:crypto";
export function verifyOnComplySignature(rawBody, signatureHeader, secret) {
if (!signatureHeader || !/^sha256=[0-9a-f]{64}$/.test(signatureHeader)) {
return false;
}
const expected =
"sha256=" +
crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
const expectedBytes = Buffer.from(expected, "utf8");
const suppliedBytes = Buffer.from(signatureHeader, "utf8");
return (
suppliedBytes.length === expectedBytes.length &&
crypto.timingSafeEqual(suppliedBytes, expectedBytes)
);
}Delivery and retries
Verify the signature over the exact raw request bytes, then return any 2xx response only after the event is durably accepted. Failed deliveries receive up to five total attempts, including the first attempt, with exponential delays beginning at five minutes. After the final failure the receiver is disabled and must be re-enabled in OnComply.
Delivery order is not guaranteed. Keep handlers idempotent and deduplicate on X-OnComply-Delivery. Manual test deliveries are sent once and are not retried. Receivers should respond within 10 seconds; redirects are not followed.
Payload examples
vendor.compliance_changedView JSON
{
"event": "vendor.compliance_changed",
"timestamp": "2026-07-28T16:20:00Z",
"vendor_id": "3b9d4a6e-1f2c-4d8b-a5e7-9c0f1a2b3c4d",
"previous_status": "pending",
"new_status": "compliant"
}vendor.invitedView JSON
{
"event": "vendor.invited",
"timestamp": "2026-07-28T16:21:00Z",
"vendor_id": "3b9d4a6e-1f2c-4d8b-a5e7-9c0f1a2b3c4d",
"invite_expires_at": "2026-07-31T16:21:00Z",
"scope": "onboarding",
"resend": false
}vendor.onboarding_completedView JSON
{
"event": "vendor.onboarding_completed",
"timestamp": "2026-07-28T16:22:00Z",
"vendor_id": "3b9d4a6e-1f2c-4d8b-a5e7-9c0f1a2b3c4d",
"queued_documents": 2,
"terms_version": "2026-03-01"
}vendor.document.uploadedView JSON
{
"event": "vendor.document.uploaded",
"timestamp": "2026-07-28T16:22:41Z",
"vendor_id": "3b9d4a6e-1f2c-4d8b-a5e7-9c0f1a2b3c4d",
"document_id": "5c6d7e8f-2a3b-4c5d-9e0f-1a2b3c4d5e6f",
"requirement_id": "0a1b2c3d-4e5f-6a7b-8c9d-0e1f2a3b4c5d",
"mime_type": "application/pdf",
"file_size": 482133
}vendor.document.failed_validationView JSON
{
"event": "vendor.document.failed_validation",
"timestamp": "2026-07-28T16:25:00Z",
"vendor_id": "3b9d4a6e-1f2c-4d8b-a5e7-9c0f1a2b3c4d",
"document_id": "5c6d7e8f-2a3b-4c5d-9e0f-1a2b3c4d5e6f",
"requirement_id": "0a1b2c3d-4e5f-6a7b-8c9d-0e1f2a3b4c5d",
"reason": "manual_review"
}vendor.expiring_soonView JSON
{
"event": "vendor.expiring_soon",
"timestamp": "2026-07-28T06:00:00Z",
"vendor_id": "3b9d4a6e-1f2c-4d8b-a5e7-9c0f1a2b3c4d",
"requirement_id": "0a1b2c3d-4e5f-6a7b-8c9d-0e1f2a3b4c5d",
"document_id": "5c6d7e8f-2a3b-4c5d-9e0f-1a2b3c4d5e6f",
"expires_at": "2026-08-27T00:00:00Z",
"due_at": "2026-08-27T00:00:00Z",
"days": 30,
"due_kind": "expiration"
}webhook.testView JSON
{
"event": "webhook.test",
"timestamp": "2026-07-28T16:30:00Z",
"tenant_id": "6d1f2e3a-4b5c-678d-9e0f-1a2b3c4d5e6f",
"webhook_id": "8e2f3a4b-5c6d-789e-0f1a-2b3c4d5e6f7a",
"delivery_id": "7f3d2a1b-9c8e-4f5a-b6d7-0e1f2a3b4c5d"
}Handle failures without losing context
Error responses include an error string and may add structured context. Every response carries an X-Request-ID; include it when contacting support.
Request bodies are limited to 2 MiB. Keep the same provider and external ID when retrying vendor resolution, and design write-side processing for responses or events that may be observed more than once.
| Status | Meaning |
|---|---|
400 | The identifier or request body is invalid. |
401 | The API key is missing, invalid, revoked, or lacks the required scope. |
402 | Workspace access is locked. Contact the workspace owner or OnComply support. |
403 | Public integration access is not enabled for the workspace. |
404 | The vendor was not found or is outside the authenticated workspace. |
409 | The request conflicts with an existing vendor record. |
413 | The request body exceeds the 2 MiB limit. |
5xx | A server error occurred. Retry safe requests with exponential backoff. |
Built-in integrations
QuickBooks Online vendor sync, MakersHub invoice intake, and SFTP document-package delivery can be configured in OnComply without custom code. Use the public endpoints above when your system needs a custom eligibility or vendor-resolution workflow.
Create your first API key
Sign in, open the API Keys page, and create a key with only the scopes your integration needs.
Sign in to get started