Integrate
Connect a provider to a project, then receive its events
Adapters belong to a single project, are managed with a signed-in session, and verify each provider's own signature on the inbound route.
Prerequisites
- A verified signed-in user with access to the organization
- An owner, admin, or developer role to create and test adapters
- An active project — the adapter is pinned to it, and every event the adapter ingests is written there
- A registered event contract for the adapter’s configured event type, in that same project
- The provider payload and secret or public key expected by the selected adapter
Working flow
1. Manage the adapter through the user session
Use Dashboard → Integrations. List and create calls are project-scoped management requests:
GET /organizations/{organization_id}/integrations
POST /organizations/{organization_id}/integrationsBoth read the x-useeventstack-project header. An adapter created while a project is active belongs to that project: it is invisible to sibling projects, and two projects in the same organization can each connect the same provider independently.
These routes use the browser session. Unsafe management requests also require the session-bound CSRF header. Event-ingestion API keys do not authorize adapter management.
2. Send the provider request to the inbound route
POST /integrations/{adapter_id}/webhookThis is an inbound adapter endpoint, not a session-management endpoint. Its body is provider-specific, and the adapter id plus the provider’s signature is the credential — no session cookie or API key is involved. The resulting event is written into the adapter’s project.
3. Use management operations from the signed-in dashboard
| Route | Current purpose |
|---|---|
POST /integrations/:adapter_id/test | Return inbound readiness or make the configured outbound test call |
GET /integrations/health?organization_id=:id | List organization integration-health records |
POST /integrations/:adapter_id/replay/:event_id | Reprocess a recorded succeeded or failed inbound diagnostic event |
POST /integrations/:adapter_id/retry/:event_id | Repeat one recorded outbound delivery from its diagnostic event |
Test, replay, and retry require a signed-in owner, admin, or developer whose active project is the adapter’s project. Calling them from a sibling project returns 404. Health reads also allow viewers in the organization.
Signature verification
There is no single shared signature format. Each provider gets the check it actually specifies.
| Provider | Credential | What is verified |
|---|---|---|
github | secret on the adapter | HMAC-SHA256 in x-hub-signature-256 over the raw body |
discord | config.public_key on the adapter | Ed25519 in x-signature-ed25519 over x-signature-timestamp concatenated with the raw body |
| any other provider | secret on the adapter | x-useeventstack-secret compared against the stored hash |
A Discord adapter needs the application’s public key from the Discord developer portal. The public key is not a secret, so it is stored in the adapter config rather than as a shared secret:
POST /organizations/{organization_id}/integrations
{
"provider": "discord",
"direction": "inbound",
"event_type": "discord.interaction_received",
"config": { "public_key": "<64 hex characters from the Discord portal>" }
}Discord validates an endpoint by sending a signed {"type": 1} PING and by sending deliberately invalid signatures. The inbound route verifies the signature before it answers, so a correctly signed PING returns {"type": 1} and an unsigned, forged, or tampered request returns 401. An adapter with no public_key rejects every request rather than accepting unverified traffic.
Expected response
When an inbound adapter accepts and processes a payload, it returns:
{
"event": { "id": "…", "type": "configured.event_type", "payload": {} },
"adapter_id": "…",
"published": true
}The test route returns a status object. Inbound adapters report ready; outbound tests report success, failed, or error with the fields produced by that operation.
Failure diagnosis
401on inbound: check the signature against the exact raw request body. Re-serialising the JSON before signing changes the bytes and invalidates the signature.401on a Discord adapter: confirmconfig.public_keyis the application public key as 64 hex characters, and that the timestamp header is sent alongside the signature.- Invalid webhook JSON: send a JSON request body accepted by the provider adapter.
- Contract error: register the adapter event type and matching version in the adapter’s project.
404on test, replay, or retry: switch to the project that owns the adapter.- Management
403: use a signed-in organization member with the required role and a valid CSRF token for unsafe requests. - Replay rejected: use a recorded
webhook.delivery.succeededorwebhook.delivery.faileddiagnostic event from that adapter. - Retry rejected: use an outbound diagnostic event and confirm the adapter still has its outbound destination.
Adapter behavior is provider-specific
Adapter behavior is specific, not universal
The current routes do not define one shared inbound envelope, an automatic retry schedule, or a general customer-facing outbound delivery contract. Validate the exact adapter and provider flow you configure.
Next step
Register the adapter’s runtime payload in Event contracts, then use Dashboard integrations to configure and test the adapter.