Integration Development

Connect an external system to erxes through GraphQL, HTTP webhooks, automation glue, or bulk import.

For headers and principals, see Authentication & Permissions. For the endpoint itself, see GraphQL API.

Choose an approach

You needUseNotes
External system reads/writes erxes dataFederated GraphQL at /graphql with erxes-app-tokenOne supergraph across core and all enabled plugins
erxes calls your system on eventsOutgoing webhook automation action, or a plugin expressRouterRetried HTTP with auth headers, configured in the workflow UI
Your system pushes events into erxesIncoming webhook automation trigger, or a plugin route (/pl:<plugin>/<path> via the gateway)POST /automation/<automationId>/<endpoint> on the automations service
Public website content/ticketscp* operations + x-app-token portal tokenSee Client Portal
Real-time updatesgraphql-ws subscriptions on wss://<gateway>/graphqlPlugin-registered resolvers over Redis
Bulk one-off data loadImport/Export module (/import-export/download-template, workers)See Import & Export
Service-to-service inside the deploymenttRPC (/<plugin>/trpc)Internal only; never expose or call across the public internet

GraphQL with an app token

Create the token once (appsAdd needs appsManage), then send it on every call:

curl --fail https://<gateway>/graphql \
  -H 'Content-Type: application/json' \
  -H 'erxes-app-token: <app-token>' \
  --data '{"query":"mutation { customersAdd(firstName:\"Ada\", primaryEmail:\"[email protected]\") { _id } }"}'

The gateway validates the token against the app_tokens collection (status: "active"), stamps lastUsedAt, and synthesizes req.user = { _id: "app:<id>", isOwner: app.allowAllPermission === true }. That satisfies checkLogin/wrapPermission resolvers; operations gated by checkPermission (such as customersAdd, which needs contactsCreate) additionally need allowAllPermission set on the app record, otherwise they fail Permission required. Revoking the app (appsRevoke/appsRemove) fails closed with 401 Invalid app token. The same record in the x-app-api-token header bypasses the CORS origin list for server-side calls from arbitrary origins.

End-to-end: create a customer and a deal

mutation CreateCustomer {
  customersAdd(
    firstName: "Ada"
    primaryEmail: "[email protected]"
    state: "customer"
  ) {
    _id
  }
}
mutation CreateDeal($customerId: String!) {
  dealsAdd(
    name: "Inbound - Ada"
    customerIds: [$customerId]
    stageId: "STAGE_ID"
    status: "active"
  ) {
    _id
    name
  }
}

dealsAdd is permission-gated (dealsAdd), so use a team-member token or an app record with allowAllPermission. Discover other operation names through introspection (dev or INTROSPECTION=true) or the plugin's graphql/schemas/.

Webhooks

Two directions exist, both centered on the automations service:

  • Incoming: an automation whose trigger type is INCOMING_WEBHOOK gets a URL of the form POST|GET|… /automation/<automationId>/<config.endpoint> on the automations service (default port 3302; reachable through the gateway's /pl: proxy when registered). The handler (incomingWebhook.ts) matches on method + endpoint and keeps a 1 MB raw body for signature validation (validateSecurity). It applies per-webhook rate limits, validates the payload against the trigger's schema, and enqueues the execution. Failed lookups and security rejections are logged to the logs service as webhook_security events. A /automation/<id>/health probe and a /<executionId>/<actionId>/continue/* resume route for waiting executions are mounted alongside.
  • Outgoing: the OUTGOING_WEBHOOK action posts to a configured URL with templated headers/body, optional auth config, and retries with backoff on 408/425/429/5xx.

Plugin HTTP endpoints also exist where a provider demands a fixed route: Frontline mounts /facebook, /instagram, /mail/receive, and /callpro routers (see Email Delivery); other provider callbacks are part of the Enterprise Edition; operation_api mounts POST /integrations/github/webhook with signature verification in githubWebhookHandler.ts. Reach any of them through the gateway's /pl:<plugin-name>/ proxy, which rewrites to the service root (for example /pl:posclient/initial-setup).

tRPC is not a public API

/<plugin>/trpc exists on every service for service-to-service calls. It is not a supported external integration point; use GraphQL or webhooks at the gateway instead.

Building your own inbound endpoint

If you are writing a plugin that must receive provider webhooks, follow the operation_api GitHub integration (modules/githubIntegration/ + utils/githubWebhookHandler.ts): mount the route on the plugin's expressRouter in startPlugin, verify the provider signature against stored config, validate the payload, scope everything by subdomain, and make the handler idempotent; providers retry.

Was this helpful?