← Back to blog

Building Workflows on Custom Field Data with Webhooks and REST API

April 4, 2026

You have set up custom fields. Your tenants are filling in data. Insurance numbers, company sizes, priority levels, shipping weights. The data is flowing in, but it is sitting in Kopra waiting to be used.

This post covers how to turn that stored data into automated workflows: verifying insurance numbers the moment they are entered, notifying sales teams when accounts grow, syncing field values back to your own database, and connecting everything to tools like Make and n8n.

How Kopra Webhooks Work

Kopra fires webhook events whenever custom field data changes. You register an endpoint URL and select which events you want to receive. When a matching event occurs, Kopra sends a POST request to your URL with the event payload.

There are 11 webhook events across four resource types:

  • Field Groups: field_group.created, field_group.updated, field_group.deleted
  • Global Fields: global_field.created, global_field.updated, global_field.deleted
  • Tenant Fields: tenant_field.created, tenant_field.updated, tenant_field.deleted
  • Field Values: field_value.saved, field_value.deleted

Every webhook delivery includes an X-Kopra-Signature header containing an HMAC-SHA256 hash of the JSON payload, signed with the endpoint's secret key. This lets you verify that the request genuinely came from Kopra and was not tampered with in transit.

If your endpoint fails (returns a non-2xx status or times out after 30 seconds), Kopra retries with exponential backoff: 1 minute, 5 minutes, 30 minutes, 2 hours, and 12 hours. Five attempts total before the delivery is marked as failed.

Registering a Webhook via the REST API

curl -X POST https://app.kopra.dev/api/webhooks \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://yourapp.com/webhooks/kopra",
    "events": ["field_value.saved", "field_value.deleted"]
  }'

The response includes a secret field. Store it securely; you will need it to verify signatures.

Workflow Ideas by Use Case

The field_value.saved event is the most useful trigger for workflows. Here are five real patterns:

Appointment booking: When a patient at Sunrise Dental fills in insurance_number, your webhook listener picks up the event and calls the insurance provider's verification API. If the policy is inactive, you flag the booking before the patient arrives.

CRM: When company_size changes from "1-10" to "51-200" for Meridian Logistics, your webhook sends a Slack notification to the sales team: "Meridian Logistics just grew past 50 employees. Time to discuss an enterprise plan."

Project management: When a priority field is set to "critical" on entity task-7291, your system creates an alert in your on-call channel so the right people see it immediately.

E-commerce: When a seller fills out product_category and shipping_weight on entity listing-4803, your webhook calculates shipping rates using your carrier's API and writes the result back through the Kopra REST API.

HR/Onboarding: When a new hire fills in tax_id and bank_account, your webhook triggers your payroll provider's enrollment flow, removing the manual data entry step from your onboarding process.

Building a Webhook Listener

Here is an Express.js endpoint that receives Kopra webhooks, verifies the signature, and routes events to handler functions:

import express from 'express';
import crypto from 'crypto';

const KOPRA_WEBHOOK_SECRET = process.env.KOPRA_WEBHOOK_SECRET!;

const app = express();
app.use(express.json());

function verifySignature(payload: object, signature: string): boolean {
  const expected = crypto
    .createHmac('sha256', KOPRA_WEBHOOK_SECRET)
    .update(JSON.stringify(payload))
    .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(signature, 'hex'),
    Buffer.from(expected, 'hex')
  );
}

app.post('/webhooks/kopra', async (req, res) => {
  const signature = req.headers['x-kopra-signature'] as string;

  if (!signature || !verifySignature(req.body, signature)) {
    return res.status(401).json({ error: 'Invalid signature' });
  }

  const { event, tenantId, fieldGroupId, entityId, fieldKey, value } = req.body;

  switch (event) {
    case 'field_value.saved':
      if (fieldKey === 'insurance_number') {
        await verifyInsurance(tenantId, entityId, value);
      }
      if (fieldKey === 'company_size' && value === '51-200') {
        await notifySalesTeam(tenantId, entityId);
      }
      break;

    case 'field_group.deleted':
      await cleanupLocalFieldData(fieldGroupId);
      break;
  }

  res.status(200).json({ received: true });
});

The timingSafeEqual comparison prevents timing attacks on the signature verification. Always return a 200 status promptly to avoid triggering Kopra's retry logic.

Connecting to Make (Integromat)

If you prefer visual workflow builders over writing code, Make works well with Kopra webhooks.

  1. Create a new scenario in Make and add a "Custom Webhook" module as the trigger. Make gives you a unique URL.
  2. Register that URL with Kopra using the REST API (as shown above), subscribing to the events you care about.
  3. Add a Router module after the webhook trigger. Each route checks the event field from the payload, so you can handle field_value.saved differently from field_group.deleted.
  4. Connect downstream modules. For a CRM notification workflow: add a Filter that checks fieldKey = "company_size" and value = "51-200", then connect a Slack module that posts to your #sales-alerts channel with the tenant ID and entity ID from the payload.
  5. For reporting, add a Google Sheets module to a separate route that appends every field_value.saved event to a spreadsheet, giving you a live audit trail of all custom field changes.

Make handles the retry and error logic on its side. Since Kopra also retries failed deliveries, you get reliable delivery from both ends.

Connecting to n8n

For teams running self-hosted automation, n8n provides similar capabilities with full control over the infrastructure.

  1. Add a Webhook node as the trigger. n8n generates a URL (use the production URL, not the test URL, when registering with Kopra).
  2. Add an IF node that branches on the event field from the incoming payload.
  3. For Slack notifications, connect a Slack node to the field_value.saved branch. Use expressions to pull tenantId, fieldKey, and value from the payload and format the message.
  4. For database sync, connect a Postgres or MySQL node that upserts the field value into your own tables. Map tenantId, entityId, fieldKey, and value to your local schema columns.
  5. For email alerts, add a Send Email node or a Gmail node with a filter on specific field keys or values. For example: only send an email when priority equals "critical".

Since n8n is self-hosted, you can deploy the webhook endpoint on the same network as your application, minimizing latency and keeping all data within your infrastructure.

Reading Field Data for Reports

Beyond webhooks (which are push-based), the REST API lets you query field values on demand. The search endpoint supports filtering by tenant, field group, field key, and value:

# Find all tenants where industry is Healthcare
curl "https://app.kopra.dev/api/field-values/search?fieldKey=industry&value=Healthcare" \
  -H "X-API-Key: YOUR_API_KEY"

# Get all entities where priority is critical
curl "https://app.kopra.dev/api/field-values/search?fieldGroupId=FG_ID&fieldKey=priority&value=critical" \
  -H "X-API-Key: YOUR_API_KEY"

# Paginate through results for a specific tenant
curl "https://app.kopra.dev/api/field-values/search?tenantId=TENANT_ID&page=2&pageSize=50" \
  -H "X-API-Key: YOUR_API_KEY"

The response includes pagination metadata (page, pageSize, total, totalPages) and each result includes the field group name and key for context. This makes it straightforward to build dashboards, generate CSV exports, or feed data into your analytics pipeline.

Syncing Custom Field Data Back to Your Database

The most common pattern combines webhooks and the REST API: use webhooks to know when data changes, and the REST API to fetch the full picture.

When your webhook listener receives a field_value.saved event, it contains the individual field that changed. But you often need all field values for that entity. Fetch them with a single API call:

curl "https://app.kopra.dev/api/tenants/sunrise-dental/field-groups/FG_ID/entities/booking-1042/values" \
  -H "X-API-Key: YOUR_API_KEY"

Then update your own database:

app.post('/webhooks/kopra', async (req, res) => {
  // ... signature verification ...

  if (req.body.event === 'field_value.saved') {
    const { tenantId, fieldGroupId, entityId } = req.body;

    // Fetch all current values for this entity
    const response = await fetch(
      `${KOPRA_API_URL}/api/tenants/${tenantId}/field-groups/${fieldGroupId}/entities/${entityId}/values`,
      { headers: { 'X-API-Key': KOPRA_API_KEY } }
    );
    const { data } = await response.json();

    // Upsert into your local database
    await db.bookingCustomFields.upsert({
      where: { bookingId: entityId },
      create: {
        bookingId: entityId,
        clinicId: tenantId,
        insuranceNumber: data.insurance_number ?? null,
        serviceType: data.service_type ?? null,
        patientNotes: data.patient_notes ?? null,
      },
      update: {
        insuranceNumber: data.insurance_number ?? null,
        serviceType: data.service_type ?? null,
        patientNotes: data.patient_notes ?? null,
      },
    });
  }

  res.status(200).json({ received: true });
});

This pattern gives you the flexibility of Kopra's dynamic field management while keeping a local copy of the data you need for queries, joins, and reporting in your own application.

What to Build Next

Webhooks and the REST API turn Kopra from a form renderer into an event-driven data layer for your application. The fields your tenants define and fill in can trigger real business logic: insurance verification, sales notifications, payroll enrollment, shipping calculations.

Start with one workflow. Pick the field that your tenants care about most, register a webhook for field_value.saved, and build something useful with it. The API documentation has the full endpoint reference, and the webhook delivery log in the Kopra dashboard shows you every event as it fires.