Skip to content

Universal API & Webhook Ingestion

Overview

CRED supports two mechanisms for ingesting external data into the import system:

Feature Webhook Ingestion Universal API
Direction Push - external systems send data to CRED Pull - CRED fetches data from external APIs
Source Type WEBHOOK UNIVERSAL_API
Trigger External HTTP POST to a webhook URL Manual (GraphQL), scheduled, or workflow
Authentication Secret-based (X-Cred-Webhook-Secret header) Basic Auth, custom headers per configuration
Entity Types CONTACT, ACCOUNT, CUSTOMER, COMMERCIAL_PRODUCT, OPPORTUNITY, DOCUMENT CONTACT, ACCOUNT, CUSTOMER, COMMERCIAL_PRODUCT, OPPORTUNITY

Both flows share the same downstream import processing pipeline: Import -> ImportRecord -> ImportField -> Entity creation.


Architecture

Database Tables

Webhook tables

Table Purpose
UniversalWebhook Webhook definitions (name, URL, secret, entity type, active status)

Universal API tables

Table Purpose
UniversalApiAccount Groups API configurations under a company (status: ACTIVE/INACTIVE)
UniversalApiConfiguration Individual endpoint configuration (URL, method, headers, auth, retry, schedule)
UniversalApiExecutionLog Audit log for each API execution (request/response details, timing, errors)

Shared import tables

Table Purpose
Import Parent record with sourceType = WEBHOOK or UNIVERSAL_API
ImportRecord Individual data rows received
ImportField Column/field definitions extracted from data

Data Flow

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    WEBHOOK FLOW                        β”‚
β”‚                                                         β”‚
β”‚  External System                                        β”‚
β”‚       β”‚                                                 β”‚
β”‚       β–Ό                                                 β”‚
β”‚  POST /universal-webhook/:ulid                          β”‚
β”‚  (X-Cred-Webhook-Secret header)                         β”‚
β”‚       β”‚                                                 β”‚
β”‚       β–Ό                                                 β”‚
β”‚  processWebhookData use case                            β”‚
β”‚       β”‚                                                 β”‚
β”‚       β”œβ”€β”€ Creates/finds Import (sourceType: WEBHOOK)    β”‚
β”‚       β”œβ”€β”€ Creates ImportRecord rows                     β”‚
β”‚       └── Auto-transitions status to UPLOADING          β”‚
β”‚                                                         β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                           β”‚
                           β–Ό
              β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
              β”‚   SHARED PIPELINE      β”‚
              β”‚                        β”‚
              β”‚  SETUP_IMPORTS task    β”‚
              β”‚  (extracts fields)     β”‚
              β”‚       β”‚                β”‚
              β”‚       β–Ό                β”‚
              β”‚  Map fields (GraphQL)  β”‚
              β”‚       β”‚                β”‚
              β”‚       β–Ό                β”‚
              β”‚  START_IMPORT_FILE_DATAβ”‚
              β”‚  (creates entities)    β”‚
              β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                           β–²
                           β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                UNIVERSAL API FLOW                       β”‚
β”‚                                                         β”‚
β”‚  executeUniversalApiRequest (GraphQL mutation)          β”‚
β”‚       β”‚                                                 β”‚
β”‚       β–Ό                                                 β”‚
β”‚  ApiRequestExecutor                                     β”‚
β”‚  (HTTP/GraphQL request to external API)                 β”‚
β”‚       β”‚                                                 β”‚
β”‚       β”œβ”€β”€ Creates/finds Import (sourceType: UNIVERSAL_API)β”‚
β”‚       β”œβ”€β”€ Creates ImportRecord rows from response       β”‚
β”‚       β”œβ”€β”€ Logs execution to UniversalApiExecutionLog    β”‚
β”‚       └── Status set to UPLOADING                       β”‚
β”‚                                                         β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Webhook Ingestion

How It Works

  1. Create a webhook via GraphQL with a target entity type (for example, CONTACT).
  2. The system generates a unique URL (/universal-webhook/<ULID>) and a secret.
  3. Configure the external system to POST JSON to this URL with X-Cred-Webhook-Secret.
  4. CRED creates (or reuses) an Import, appends ImportRecord rows, and sets status to UPLOADING.

Webhook URL Format

https://<host>/universal-webhook/<ULID>

Accepted Payload Formats

Array at root

[
  { "firstName": "John", "email": "john@example.com" },
  { "firstName": "Jane", "email": "jane@example.com" }
]

Array in data field

{
  "data": [
    { "firstName": "John", "email": "john@example.com" }
  ]
}

Document Webhooks

For DOCUMENT entity type webhooks, two additional endpoints are available:

  • POST /universal-webhook/:ulid/document for multipart file upload (PDF only)
  • POST /universal-webhook/:ulid/document-json for JSON payload with base64 PDF or public URL

GraphQL Operations

Create webhook

mutation {
  createWebhook(input: {
    name: "My Webhook"
    entityType: CONTACT
    description: "Receives contacts from external CRM"
  }) {
    id
    url
    secret
  }
}

Update webhook

mutation {
  updateWebhook(input: {
    id: 1
    name: "Updated Name"
  }) {
    id
    name
  }
}

Delete webhook

mutation {
  deleteWebhook(id: 1)
}

Universal API

How It Works

  1. Create a Universal API Account as a company-level grouping.
  2. Create one or more Configurations under that account (per endpoint).
  3. Use test connection to validate connectivity without saving data.
  4. On execution (manual, schedule, workflow), CRED fetches data, creates Import + ImportRecord, and logs execution.

Supported Request Types

Type Description
HTTP Standard REST API calls (GET/POST)
GRAPHQL GraphQL requests (body with query and variables)

Configuration Options

Field Required Description
universalApiAccountId Yes Parent account ID
requestType Yes HTTP or GRAPHQL
entityType Yes Target entity type (CONTACT, ACCOUNT, etc.)
url Yes API endpoint URL
method No GET or POST (default: GET for HTTP)
headers No Custom headers as JSON string
queryString No Query parameters as JSON string
body No Request body as JSON string
bodyType No JSON, XML, form-data, x-www-form-urlencoded, GRAPHQL
basicAuthUsername No Basic Auth username
basicAuthPassword No Basic Auth password (encrypted at rest)
timeoutSeconds No Request timeout (1-300 seconds)
retryCount No Number of retries on failure (0-10)
retryDelayMillis No Delay between retries in ms (0-60000)
scheduleEnabled No Enable scheduled execution (runs 3x daily)

Execution Triggers

Trigger Description
MANUAL Triggered via executeUniversalApiRequest GraphQL mutation
SCHEDULE Automatic execution when scheduleEnabled is true
WORKFLOW Triggered by workflow action EXECUTE_UNIVERSAL_API_REQUEST

Execution Logging

Each execution (success or failure) is stored in UniversalApiExecutionLog with:

  • Request details (URL, method, headers, body, query params)
  • Response details (status code, headers)
  • Timing (startedAt, endedAt, executionTime)
  • Created record count
  • Error details (if any, truncated to 500 chars)
  • Trigger metadata (type and source)

Logs older than 90 days are cleaned up automatically.

GraphQL Operations

Create account

mutation {
  createUniversalApiAccount(input: {
    status: ACTIVE
    description: "My API integrations"
  }) {
    id
  }
}

Create configuration

mutation {
  createUniversalApiConfiguration(input: {
    universalApiAccountId: 1
    requestType: HTTP
    entityType: CONTACT
    url: "https://api.example.com/contacts"
    method: GET
    headers: "{\"Authorization\": \"Bearer token123\"}"
    timeoutSeconds: 30
    retryCount: 3
    retryDelayMillis: 1000
  }) {
    id
  }
}

Test connection (no data imported)

mutation {
  testUniversalApiConnection(input: {
    requestType: HTTP
    url: "https://api.example.com/contacts"
    method: GET
    headers: "{\"Authorization\": \"Bearer token123\"}"
  }) {
    success
    statusCode
    executionTime
    parsedData
  }
}

Execute request (imports data)

mutation {
  executeUniversalApiRequest(universalApiConfigurationId: 1) {
    success
    statusCode
    recordsCreated
    executionTime
  }
}

Query accounts and configurations

query {
  universalApiAccounts(first: 10) {
    edges {
      node {
        id
        status
        description
        configurations {
          id
          url
          method
          entityType
          scheduleEnabled
        }
      }
    }
  }
}

Delete configuration/account

mutation { deleteUniversalApiConfiguration(id: 1) }
mutation { deleteUniversalApiAccount(id: 1) }

Import Processing Pipeline

After ingestion from webhook or Universal API, data flows through the standard import pipeline.

Step 1: SETUP_IMPORTS (Worker Task)

  • Extracts keys from the first ImportRecord
  • Creates one ImportField per unique key

Step 2: Field Mapping

Users map extracted fields to CRED data descriptions:

mutation {
  setImportFields(importId: 1, fields: [
    { id: 10, dataDescriptionId: 5 }
  ]) {
    id
  }
}

Step 3: START_IMPORT_FILE_DATA (Worker Task)

  • Reads mapped fields
  • Creates/updates target entities (Contact, Account, and others)
  • Tags entities by source type (IMPORT_WEBHOOK or IMPORT_UNIVERSAL_API)
  • Updates ImportRecord.entityId and ImportRecord.isImported

Security

Webhook Security

  • Each webhook has a unique cryptographic secret
  • Requests must include X-Cred-Webhook-Secret
  • Secret check uses constant-time comparison (crypto.timingSafeEqual)
  • Inactive webhooks reject requests with HTTP 403

Universal API Security

  • Basic Auth credentials are encrypted at rest
  • Configurations are tenant-scoped to company
  • SSRF protections block private/internal/loopback targets
  • Company-level authorization is enforced across all operations

Enums Reference

ImportSourceTypeEnum

Value Usage
FILE CSV/file uploads
EMAIL Email imports
WEBHOOK Webhook ingestion
UNIVERSAL_API Universal API pull
MERGE_* CRM sync integrations

UniversalApiExecutionStatus

Value Description
PENDING Execution queued
RUNNING Execution in progress
SUCCESS Completed successfully
FAILED Completed with errors

UniversalApiAccountStatus

Value Description
ACTIVE Account can execute configurations
INACTIVE Account is disabled and executions are skipped