Integrations Live
Third-party connections live behind a single resource. OAuth-based integrations (Shopify, Stripe, HubSpot, Salesforce, Cloudflare) return an auth URL; API-key integrations (Slack incoming webhook, generic webhook) take credentials directly. List, connect, configure, and disconnect from one consistent surface.
List integrations
Returns all configured integrations for the workspace. Filters: kind, status (connected, pending, disconnected).
Code samples
curl 'https://app.message.com/api/v1/integrations' \
-H 'Authorization: Bearer YOUR_WORKSPACE_JWT'const res = await fetch('https://app.message.com/api/v1/integrations', {
headers: { Authorization: 'Bearer ' + token }
});
const { integrations } = await res.json();import requests
r = requests.get(
"https://app.message.com/api/v1/integrations",
headers={"Authorization": f"Bearer {token}"},
)
integrations = r.json()["integrations"]require "net/http"
require "json"
uri = URI("https://app.message.com/api/v1/integrations")
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
req = Net::HTTP::Get.new(uri)
req["Authorization"] = "Bearer #{token}"
http.request(req)
end
integrations = JSON.parse(res.body)["integrations"]<?php
$ctx = stream_context_create([
"http" => ["method" => "GET", "header" => "Authorization: Bearer $token"]
]);
$integrations = json_decode(file_get_contents("https://app.message.com/api/v1/integrations", false, $ctx), true)["integrations"];{
"id": "uuid",
"kind": "shopify",
"status": "connected",
"config": {
"shopDomain": "acme.myshopify.com",
"scopes": ["read_orders", "read_customers", "read_products"]
},
"connectedAt": "2026-05-13T15:30:00Z",
"createdAt": "2026-05-13T15:30:00Z"
}Available integration kinds
| Kind | Mode | Status |
|---|---|---|
shopify | OAuth | Coming soon |
stripe | OAuth | Planned |
wordpress | OAuth | Coming soon |
cloudflare | OAuth | Planned |
hubspot | OAuth | Planned |
salesforce | OAuth | Planned |
slack_webhook | API key | Live |
generic_webhook | API key | Live |
Start a connection
For OAuth kinds, returns an auth URL you redirect the user to. After they approve, the provider redirects to our callback, which completes the connection and notifies the dashboard. For API-key kinds, takes credentials in the body and returns status: connected immediately.
| Field | Type | Description |
|---|---|---|
| kindrequired | enum | One of the kinds in the table above. |
| configoptional | object | Optional kind-specific config (e.g., Shopify shop domain). OAuth kinds discover most config from the provider; API-key kinds carry credentials here. |
{
"authUrl": "https://acme.myshopify.com/admin/oauth/authorize?client_id=...&scope=...&state=..."
}Update an integration
Update integration-specific config (e.g., which Shopify scopes to use, which Slack channel to post to). Requires expectedVersion.
Disconnect
Disconnects the integration. For OAuth integrations, we also revoke the access token on the provider side. Inbound data already synced is preserved; future syncs stop immediately.
Test a connection
Pings the upstream provider to verify the credentials still work. Used by the dashboard "test connection" button. Returns { "ok": true } or the underlying error.
Errors
| Code | When |
|---|---|
400 invalid_body | Kind unknown or config invalid for kind. |
402 plan_limit | Workspace plan does not include the requested kind. |
403 forbidden | Agent role is not admin. |
409 conflict | An integration of this kind already exists; some kinds are singletons. |
422 oauth_failed | Provider rejected the OAuth callback. Inspect detail.providerError. |
Common pitfalls
- Forgetting to handle the OAuth redirect. The auth URL goes to the provider, then back to our callback. Your client polls
/integrations/:idforstatus: connected. - Storing API keys in
configfor OAuth kinds. Ignored. OAuth flows persist tokens server-side. - Mixing test and production credentials. Use a separate workspace (or wait for sandbox API keys) for staging.