Help CenterAutomation & Integrations

API & Webhooks V2 Documentation

The 360Onboard V2 API gives your server structured access to published flows, clients, onboarding progress, and completed responses. Webhook V2 sends rich snapshots when a flow is published, a client is created, or a client completes onboarding.

Existing V1 integrations continue to work unchanged. New integrations should use V2. You do not need to migrate an existing integration until you are ready.

Authentication

Generate an API key in Settings → APIs, Webhooks & MCP → API Keys. Public V2 requests pass that key in the apiKey query parameter.

GET https://api.360onboard.com/v2/workspaces?apiKey=sk_live_your_key_here

Base URL: https://api.360onboard.com/v2

Keep API keys on your server. Do not embed them in browser JavaScript, mobile applications, or public repositories.

API keys are account-level. After authenticating, list workspaces to find the workspaceId used by the remaining resource routes.

Discover Your Workspace

curl "https://api.360onboard.com/v2/workspaces?apiKey=sk_live_your_key_here"
{
  "object": "list",
  "data": [
    {
      "id": "workspace_uuid",
      "object": "workspace",
      "name": "Acme Agency",
      "slug": "acme-agency",
      "api_url": "https://api.360onboard.com/v2/workspaces/workspace_uuid"
    }
  ]
}

Every workspace resource is scoped to the API-key owner. A workspace that does not belong to the key returns 404 rather than exposing whether another account owns it.

API Endpoints

Append ?apiKey=sk_live_your_key_here to every request. When a URL already has query parameters, append the key with &apiKey=....

Flows

MethodEndpointPurpose
GET/workspaces/{workspaceId}/flowsList published V2 flows. Supports page and limit up to 100.
GET/workspaces/{workspaceId}/flows/{flowId}Get a complete sanitized flow, including steps, design, and public URL.
GET/workspaces/{workspaceId}/flows/{flowId}/statsGet total, completed, in-progress, and not-started clients plus completion rate.

A rich flow contains its name, type, schema version, published status, design configuration, public-link settings, final public URL, and sanitized step definitions. API headers, webhook secrets, outbound URLs, static recipients, and private integration target IDs are not exposed.

{
  "data": {
    "id": "flow_uuid",
    "object": "flow",
    "name": "Client Onboarding",
    "type": "client",
    "status": "published",
    "schema_version": 2,
    "public_url": "https://onboard.acme.com/start",
    "step_count": 4,
    "steps": [
      {
        "id": "step-1",
        "object": "flow_step",
        "position": 0,
        "type": "questionnaire",
        "title": "Company details",
        "fields": []
      }
    ]
  }
}

Clients

MethodEndpointPurpose
GET/workspaces/{workspaceId}/clientsList V2 clients. Supports flow_id, page, and limit.
POST/workspaces/{workspaceId}/clientsCreate a client in a published V2 flow and optionally send the invitation.
GET/workspaces/{workspaceId}/clients/{clientId}Get the client, progress, complete flow, and final onboarding URL.
PATCH/workspaces/{workspaceId}/clients/{clientId}Update supplied identity fields.
DELETE/workspaces/{workspaceId}/clients/{clientId}Soft-delete the client onboarding record.
GET/workspaces/{workspaceId}/clients/{clientId}/progressGet completion percentage, step counts, response ID, and activity timestamps.
POST/workspaces/{workspaceId}/clients/{clientId}/remindSend an invitation reminder unless the client is already complete.

Create a client:

curl -X POST \
  "https://api.360onboard.com/v2/workspaces/workspace_uuid/clients?apiKey=sk_live_your_key_here" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: acme-client-2026-08-27" \
  -d '{
    "flow_id": "flow_uuid",
    "email": "jane@acme.com",
    "first_name": "Jane",
    "last_name": "Smith",
    "company_name": "Acme Corp",
    "phone": "+15551234567",
    "send_invitation": true
  }'

flow_id and email are required. send_invitation defaults to true.

The returned client includes the complete associated flow and the final onboarding URL. When the workspace has an appropriate verified custom domain, that custom-domain URL is returned.

Safe Retries with Idempotency-Key

Client creation supports an optional Idempotency-Key header for 24 hours.

  • Retrying the same request with the same key returns the original response without creating another client.
  • Reusing the key with different request data returns a conflict instead of performing an ambiguous action.
  • Use a unique key for every logical client-creation operation.

This is especially important for automation platforms that retry after timeouts and cannot tell whether the first request succeeded.

Responses

MethodEndpointPurpose
GET/workspaces/{workspaceId}/responsesList rich V2 responses. Supports flow_id, client_id, and status.
GET/workspaces/{workspaceId}/responses/{responseId}Retrieve one response with its related client, flow, and typed step results.

Response steps preserve their real type instead of flattening everything into generic key/value data. Depending on the flow, a response can include:

  • Questionnaire answers and uploaded files
  • E-signature signers, signed-document URL, audit events, timestamps, and PDF hash
  • Payment amount, currency, provider, and payment time
  • Platform access grants and completed guided-access steps
  • Scheduling results
  • AI and API outputs
  • CRM, export, email, SMS, webhook, and client-portal action results

Ephemeral-field values and outbound integration secrets are never included.

{
  "id": "provider-file-id",
  "object": "file",
  "name": "brand-assets.zip",
  "size_bytes": 824193,
  "mime_type": "application/zip",
  "storage_provider": "google_drive",
  "url": "https://drive.google.com/file/d/final-file-id",
  "uploaded_at": "2026-08-27T18:00:00Z"
}

Webhooks V2

Webhooks let 360Onboard notify your server instead of requiring you to poll the API. V2 intentionally supports three high-value events:

EventWhen it firesIncluded data
flow.createdA V2 flow is published for the first time.Complete sanitized flow, steps, design, timestamps, and public URL.
client.createdA client onboarding is created.Client identity, progress, complete flow, and final onboarding URL.
client.completedA client completes the flow and final storage processing settles.Client, flow, typed response steps, uploads, signed documents, payments, grants, and audit information.

Configure an endpoint in Settings → APIs, Webhooks & MCP → Webhooks, or use the webhook-endpoint API described below. New V2 endpoint URLs must use public HTTPS addresses.

Event Envelope and Headers

Every webhook is an HTTPS POST containing an immutable event snapshot.

POST https://your-server.com/360onboard/webhook
Content-Type: application/json
User-Agent: 360Onboard-Webhooks/2.0
X-360Onboard-Webhook-Id: event_uuid
X-360Onboard-Event: client.completed
X-360Onboard-Timestamp: 1787853600
X-360Onboard-Signature: t=1787853600,v1=a1b2c3...
X-360Onboard-Delivery-Attempt: 1
{
  "id": "event_uuid",
  "object": "event",
  "api_version": "v2",
  "type": "client.completed",
  "workspace_id": "workspace_uuid",
  "created_at": "2026-08-27T18:00:00Z",
  "data": {
    "client": {},
    "flow": {},
    "response": {}
  }
}

flow.created

{
  "type": "flow.created",
  "data": {
    "flow": {
      "id": "flow_uuid",
      "name": "Q3 Client Onboarding",
      "status": "published",
      "public_url": "https://onboard.acme.com/q3-onboarding",
      "steps": []
    }
  }
}

client.created

{
  "type": "client.created",
  "data": {
    "client": {
      "id": "client_uuid",
      "full_name": "Jane Smith",
      "email": "jane@acme.com",
      "company_name": "Acme Corp",
      "flow_id": "flow_uuid",
      "flow_name": "Q3 Client Onboarding",
      "url": "https://onboard.acme.com/jane-smith",
      "progress": {},
      "flow": {}
    }
  }
}

client.completed

{
  "type": "client.completed",
  "data": {
    "client": {},
    "flow": {},
    "response": {
      "id": "response_uuid",
      "status": "completed",
      "completed_at": "2026-08-27T18:00:00Z",
      "steps": [
        {
          "id": "upload-step",
          "type": "questionnaire",
          "status": "completed",
          "fields": [
            {
              "id": "brand_assets",
              "label": "Upload your brand assets",
              "type": "file",
              "value": {
                "object": "file",
                "name": "brand-assets.zip",
                "storage_provider": "google_drive",
                "url": "https://drive.google.com/file/d/final-file-id"
              }
            }
          ]
        },
        {
          "id": "contract-step",
          "type": "esignature",
          "status": "completed",
          "signers": [],
          "document": {
            "status": "completed",
            "url": "https://drive.google.com/file/d/final-contract-id",
            "storage_provider": "google_drive",
            "sha256": "document_hash",
            "finalized_at": "2026-08-27T17:59:58Z"
          },
          "audit_events": []
        }
      ]
    }
  }
}

Final Cloud-Storage URLs

For client.completed, 360Onboard waits while queued or active cloud-storage transfers settle. When mirroring succeeds, uploaded files and signed documents use the final connected-provider URL. If no provider is connected or a final provider URL is unavailable, the payload retains a durable 360Onboard URL.

Delivery History and Replay

V2 records every delivery attempt, including its status, attempt count, next retry time, HTTP response status/body, error, timestamps, and original event payload.

In 360Onboard, open Settings → Developer → Webhooks. Each V2 endpoint shows its ten most recent deliveries with the event type, success or failure, HTTP response code, and delivery time. Select a delivery to expand its attempts, request payload, response body, and error. Select Replay to send the original immutable event again.

Delivery history and replay are also available through the V2 API. The log lives in 360Onboard; it is not installed on the receiving website. A receiving application can maintain its own inbound log if desired.

GET /workspaces/{workspaceId}/webhook-endpoints/{endpointId}/deliveries

Use limit to request up to 100 recent deliveries.

{
  "object": "list",
  "data": [
    {
      "id": "delivery_uuid",
      "object": "webhook_delivery",
      "event_id": "event_uuid",
      "event_type": "client.completed",
      "status": "succeeded",
      "attempts": 1,
      "response_status": 200,
      "response_body": "ok",
      "last_error": null,
      "delivered_at": "2026-08-27T18:00:02Z",
      "payload": {}
    }
  ]
}

Replay a delivery:

POST /workspaces/{workspaceId}/webhook-endpoints/{endpointId}/deliveries/{deliveryId}/replay

Replay resets the delivery attempt and sends the original immutable event payload again. It does not rebuild the event from current database values.

Design receivers to be idempotent by storing X-360Onboard-Webhook-Id. If that event ID has already been processed, return a successful response without performing the downstream action twice.

Automatic Retries

Network errors and non-2xx responses are retried up to eight total attempts. The retry schedule is approximately:

  1. 1 minute
  2. 5 minutes
  3. 15 minutes
  4. 1 hour
  5. 4 hours
  6. 12 hours
  7. 24 hours

Return a 2xx response promptly after safely accepting the event. Perform slow downstream work asynchronously on your own system.

Verifying Webhook Signatures

Webhook V2 signatures are always enabled. Save the whsec_... secret shown when the endpoint is created.

To verify a request:

  1. Read the raw request body without parsing or reformatting it.
  2. Read X-360Onboard-Timestamp and reject stale timestamps.
  3. Build the signed value as timestamp.rawBody.
  4. Calculate HMAC-SHA256 with the endpoint secret.
  5. Compare the hexadecimal result with the v1 value in X-360Onboard-Signature using a timing-safe comparison.
import crypto from "node:crypto";

export function verify360OnboardWebhook(rawBody, headers, secret) {
  const timestamp = headers.get("X-360Onboard-Timestamp");
  const signatureHeader = headers.get("X-360Onboard-Signature") || "";
  const received = signatureHeader
    .split(",")
    .find((part) => part.startsWith("v1="))
    ?.slice(3);

  if (!timestamp || !received) return false;

  const ageSeconds = Math.abs(Date.now() / 1000 - Number(timestamp));
  if (!Number.isFinite(ageSeconds) || ageSeconds > 300) return false;

  const expected = crypto
    .createHmac("sha256", secret)
    .update(`${timestamp}.${rawBody}`)
    .digest("hex");

  const receivedBuffer = Buffer.from(received, "hex");
  const expectedBuffer = Buffer.from(expected, "hex");
  return receivedBuffer.length === expectedBuffer.length &&
    crypto.timingSafeEqual(receivedBuffer, expectedBuffer);
}

Managing Webhook Endpoints Through the API

MethodEndpointPurpose
GET/workspaces/{workspaceId}/webhook-endpointsList V2 endpoints.
POST/workspaces/{workspaceId}/webhook-endpointsRegister an endpoint. The signing secret is returned once.
PATCH/workspaces/{workspaceId}/webhook-endpoints/{endpointId}Update URL, subscribed events, or active state.
DELETE/workspaces/{workspaceId}/webhook-endpoints/{endpointId}Delete an endpoint and its delivery history.
POST/workspaces/{workspaceId}/webhook-endpoints/{endpointId}/rotate-secretRotate the signing secret and return the replacement once.
GET/workspaces/{workspaceId}/webhook-endpoints/{endpointId}/deliveriesInspect delivery attempts and original payloads.
POST/workspaces/{workspaceId}/webhook-endpoints/{endpointId}/deliveries/{deliveryId}/replayReplay the original event.

Create an endpoint:

curl -X POST \
  "https://api.360onboard.com/v2/workspaces/workspace_uuid/webhook-endpoints?apiKey=sk_live_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-server.com/360onboard/webhook",
    "events": ["flow.created", "client.created", "client.completed"]
  }'

The response contains the endpoint plus its signing secret. Store the secret immediately because it is not shown again. If it is lost, use the rotate-secret endpoint.

Errors and Rate Limits

Errors use a stable machine-readable code and human-readable message:

{
  "error": {
    "code": "response_not_found",
    "message": "Response not found."
  }
}

Common status codes:

  • 200 — successful read or action
  • 201 — resource created
  • 400 — invalid request
  • 401 — missing or invalid API key
  • 404 — workspace or resource not found
  • 409 — idempotency conflict
  • 429 — rate limit exceeded
  • 500 — server error

The public API allows 100 requests per minute per API key. Responses include X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset. A 429 response also includes Retry-After.

Use webhooks for real-time updates instead of repeatedly polling responses.

Support: david@360onboard.com

Last updated on 2026-08-27