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 need | Use | Notes |
|---|---|---|
| External system reads/writes erxes data | Federated GraphQL at /graphql with erxes-app-token | One supergraph across core and all enabled plugins |
| erxes calls your system on events | Outgoing webhook automation action, or a plugin expressRouter | Retried HTTP with auth headers, configured in the workflow UI |
| Your system pushes events into erxes | Incoming webhook automation trigger, or a plugin route (/pl:<plugin>/<path> via the gateway) | POST /automation/<automationId>/<endpoint> on the automations service |
| Public website content/tickets | cp* operations + x-app-token portal token | See Client Portal |
| Real-time updates | graphql-ws subscriptions on wss://<gateway>/graphql | Plugin-registered resolvers over Redis |
| Bulk one-off data load | Import/Export module (/import-export/download-template, workers) | See Import & Export |
| Service-to-service inside the deployment | tRPC (/<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_WEBHOOKgets a URL of the formPOST|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 aswebhook_securityevents. A/automation/<id>/healthprobe and a/<executionId>/<actionId>/continue/*resume route for waiting executions are mounted alongside. - Outgoing: the
OUTGOING_WEBHOOKaction 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.