SCSuper ConnectDocumentationOpen the admin panel →

Receive provider events

Get Slack and GitHub webhooks into your backend without holding a provider signing secret or mapping provider accounts to your users yourself.

Each integration has a receive URL. Slack or GitHub posts to it, Super Connect verifies the request with the integration's inbound secret, works out which connections the event concerns, answers the provider within its deadline, and then delivers the event to your integration's webhook URL as a signed forward event. Every request, accepted or not, is logged on the integration page in the admin panel.

Each provider template ships a webhook adapter that verifies its requests, answers its handshakes and chooses which connections receive an event. Slack and GitHub have adapters today. An integration on a provider whose template has none has a receive URL that answers 404. The API tells you: GET /api/v1/integrations returns webhooks with the adapter's setup instructions and the label of the secret it expects, or null.

Before you start you need an integration on the slack or github preset, an organization key, and an HTTPS endpoint in your app. If your endpoint does not yet verify Super Connect's signature, read Receive webhooks first: forward events use the same headers and signature as auth events.

1. Configure the integration

Forwarding needs two things on the integration: an inbound secret, so requests from the provider can be verified, and a webhook URL, so verified events have somewhere to go.

curl https://super-connect.dogar.biz/api/v1/integrations/slack -X PUT \
  -H "Authorization: Bearer $SUPER_CONNECT_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{
    "providerId": "slack",
    "scopes": "channels:read chat:write users:read",
    "webhookUrl": "https://app.example.com/super-connect/events",
    "webhookSecret": "GENERATE_A_LONG_RANDOM_STRING",
    "inboundSecret": "THE_PROVIDER_SIGNING_SECRET"
  }'

The response carries receiveId. Your receive URL is https://super-connect.dogar.biz/webhook/<receiveId>. In the admin panel, open the integration and use the copy button under Received.

2. Register the receive URL with the provider

Slack

Slack calls the Request URL the moment you save it, so the secret must be in place first. A request that arrives before the secret is saved is rejected with 401, and Slack marks the URL as failing.

  1. In your Slack app, open Basic Information and copy the Signing Secret.
  2. Store it as the integration's inbound secret, in the admin panel or with the PUT request above.
  3. In Slack, open Event Subscriptions and set the Request URL to the receive URL. Super Connect answers the url_verification handshake, and Slack shows a green check.
  4. Open Interactivity & Shortcuts and set the same receive URL. Events, interactivity and slash commands all route through one URL.
  5. Under Subscribe to events, choose only workspace-wide public-channel events (message.channels, channel_created, user_change and the like) and the user's own events (channel_joined, channel_left, dnd_updated, manual_presence_change, pref_change, user_status_changed…). Do not subscribe to message.groups, message.im, message.mpim, reactions, files or pins: Slack sends those to whoever can see the content, which connection metadata cannot express, so they would reach the wrong connections.

GitHub

GitHub webhooks are created per repository or per organization, and each one carries its own secret. Point them all at one receive URL and give them all the same secret, because the integration stores a single inbound secret.

  1. In the repository or organization settings, open Webhooks and add a webhook.
  2. Set the payload URL to the receive URL and the content type to application/json.
  3. Set the secret to the integration's inbound secret. Reuse that one secret for every hook a customer points at this integration. A customer who cannot share one secret needs a second integration.
  4. Choose the events and save. GitHub sends a ping at once. Super Connect answers it with 200 and logs it; a ping is never forwarded.

3. Make events find connections

An event is delivered once per recipient: a connection the provider's adapter chose from the integration's connections and their metadata. An event with no recipients is not delivered at all. How the adapter chooses depends on the provider.

Slack routes on the workspace or the user, automatically. When a user connects, Super Connect copies team.id and authed_user.id from Slack's token response into the connection's metadata. A workspace-wide event goes to every connection whose team.id equals the event's team_id. A personal event such as channel_joined or dnd_updated goes to the one connection whose authed_user.id equals the event's authorizations[0].user_id. Interactivity payloads and slash commands route by workspace. You do not set these keys, and the API refuses an attempt to.

Connections made before authed_user.id was captured hold only team.id; they receive workspace events but no personal events until the user reconnects.

GitHub routes on metadata you set. A GitHub payload carries no Super Connect identity, so tell the connection which repositories and organizations it stands for, one key per id:

curl https://super-connect.dogar.biz/api/v1/connections/$CONNECTION_ID \
  -X PATCH \
  -H "Authorization: Bearer $SUPER_CONNECT_API_KEY" \
  -H "X-User-Id: user_123" \
  -H 'Content-Type: application/json' \
  -d '{ "metadata": { "repository.612345678": "1", "organization.9876543": "1" } }'

With the SDK: client.connections.setMetadata(connectionId, metadata, userId).

4. Handle the forward event

Your endpoint receives one request per matched connection:

{
  "id": "evt_9f21…",
  "version": "1",
  "type": "forward",
  "provider": "slack",
  "integrationId": "slack",
  "connectionId": "conn_4b0e…",
  "endUserId": "user_123",
  "receivedAt": "2026-09-13T09:15:00.000Z",
  "eventType": "message",
  "providerEventId": "Ev09ABCDEF",
  "headers": {
    "content-type": "application/json"
  },
  "payload": {}
}

Then, in order:

  1. Verify the request exactly as you verify auth events, with X-Super-Connect-Webhook-Timestamp and X-Super-Connect-Hmac-Sha256 over timestamp + "." + rawBody and your webhook secret. Branch on type after verifying.
  2. Answer 2xx quickly. Delivery is retried on network errors and 5xx after 3 and 6 seconds, then marked failed.
  3. Deduplicate on providerEventId, not on id. Providers resend: Slack retries up to three times when it gets no answer within three seconds, and GitHub reuses X-GitHub-Delivery on a manual redelivery. Each resend is a fresh request to Super Connect with a fresh id. When providerEventId is null, fall back to id, which still covers Super Connect's own retries. A Slack retry carries x-slack-retry-num in headers.
  4. Do not expect unmatched events. A verified event with no recipients is logged as unmatched and never delivered, so connectionId and endUserId are always set. A workspace or repository your customer never connected shows up in the received log, not at your endpoint.

[!IMPORTANT] Slack interactivity is answered with an empty 200 before the event is forwarded, so you cannot return a response_action. A view_submission closes the modal; validation errors, update and push are not available through forwarding. Update the view afterwards with views.update.

5. Read the received log

Every request to a receive URL is logged, verified or not, with its raw body kept for seven days. The admin panel shows the log on the integration page under Received, with each row's headers, body and the forwards it produced. The same rows are available from GET /api/v1/integrations/:id/webhooks.

Outcome What happened
forwarded Verified and the adapter chose at least one recipient; matched counts them
unmatched Verified but the adapter chose no recipient; nothing delivered
filter_failed Verified but the adapter failed while choosing recipients; error holds the message
replied Answered directly: a Slack challenge or a GitHub ping
rejected Refused before routing, with error naming the reason
too_large The body was over 1 MiB and was not read

A rejected row with a 401 usually means the secret in the provider's console and the integration's inbound secret differ, or the secret was saved after the provider's first call.

6. Develop locally

The local Worker listens on port 4242. A tunnel to it gives you a public receive URL to paste into Slack or GitHub.

npm run dev
cloudflared tunnel --url http://localhost:4242

Use the tunnel's HTTPS host in place of https://super-connect.dogar.biz, both in the receive URL you give the provider and in the webhookUrl on the integration.

To replay an event without touching the provider, fetch a logged body and post it back with a fresh signature. The original headers cannot be reused: Super Connect verifies every request against the stored secret, and Slack timestamps older than five minutes are rejected.

curl "$SUPER_CONNECT/api/v1/integrations/slack/webhooks/$INBOUND_ID" \
  -H "Authorization: Bearer $SUPER_CONNECT_API_KEY" | jq -r .body > event.json

BODY=$(cat event.json)
TIMESTAMP=$(date +%s)
SIGNATURE="v0=$(printf 'v0:%s:%s' "$TIMESTAMP" "$BODY" |
  openssl dgst -sha256 -hmac "$SLACK_SIGNING_SECRET" -r | cut -d' ' -f1)"

curl "$TUNNEL/webhook/$RECEIVE_ID" -X POST \
  -H 'Content-Type: application/json' \
  -H "X-Slack-Request-Timestamp: $TIMESTAMP" \
  -H "X-Slack-Signature: $SIGNATURE" \
  --data "$BODY"

For GitHub, sign the body alone with no timestamp and send it as X-Hub-Signature-256: sha256=<hex>, with the event name in X-GitHub-Event.