Connection setup
testConnection, setup instructions, and webhook URL templates — the install flow for a connector.
A connector needs to tell the platform how to:
- Test that an admin’s credentials work.
- Walk the admin through any provider-side setup (creating a webhook endpoint, generating an API key, etc.).
- Generate a per-installation webhook URL the admin can paste into the provider.
These all live in the manifest’s connection block.
For providers that expect OAuth, add connection.oauth and let the host manage redirects, callback state, token exchange, encrypted token storage, refresh, disconnect, and reauth status. See OAuth for the full contract.
Shape
connection: {
testConnection: {
route: "/setup/test_connection",
method: "POST",
},
setup: {
webhookUrlTemplate:
"/api/v1/extensions/<tenant_id>/stripe/routes/webhooks/stripe?connection_id=<connection_id>",
instructions:
"Create a Stripe webhook endpoint that forwards must-have accounting events to the generated Backfill URL, then paste the Stripe webhook signing secret here.",
},
settingsSchema: { /* JSON Schema — see Settings schema */ },
},
testConnection
Points at a route in your src/api/ tree. The dashboard’s setup screen calls this route after the admin enters credentials and clicks “Test connection”. Return 200 if the credentials work, an explicit error response otherwise.
// src/api/setup/test_connection.ts
import { api, Http } from "@backfill-io/sdk";
export const config = { auth: "api_key" };
export const POST = api(async (request) => {
const apiKey = request.body.api_key;
const res = Http.get("https://api.stripe.com/v1/balance", {
auth: { bearer: apiKey },
});
if (!res.ok) {
return api.badRequest("Invalid Stripe API key", { status: res.status });
}
return api.json({ ok: true, account: res.body.account_id });
});
The route is a normal API route — exactly the pattern in API routes. The only thing special is that it’s referenced from the connection block, which causes the dashboard to surface it during setup.
For OAuth connectors, the same route usually becomes the postConnect route. It should call OAuth.accessToken(), verify the provider account, and return stable provider metadata:
connection: {
testConnection: { route: "/setup/test_connection", method: "POST" },
oauth: {
type: "authorization_code",
authorizationUrlTemplate: "https://provider.example.com/oauth/authorize",
tokenUrlTemplate: "https://provider.example.com/oauth/token",
scopes: ["orders.read"],
client: { credentialKey: "provider_public_app" },
postConnect: { route: "/setup/test_connection", method: "POST" },
},
}
setup.webhookUrlTemplate
A URL pattern with placeholder tokens that the platform expands at install time. The admin sees the expanded URL in the dashboard and pastes it into the provider’s webhook configuration.
| Token | Replaced with |
|---|---|
<tenant_id> | The installing workspace’s tenant ID. |
<connection_id> | The newly-created connection’s ID. |
The expanded URL hits your connector’s webhook endpoint — see Webhooks.
setup.instructions
Free-form admin-facing copy explaining what they need to do on the provider side. Markdown is supported in the dashboard renderer (consult the platform team for the exact subset).
Setup flow, end to end
- Admin clicks “Install Stripe” in the connectors gallery.
- Dashboard renders a form from
connection.settingsSchema(see Settings schema). - Admin enters credentials. Clicks “Test connection”. Dashboard POSTs to
connection.testConnection.route. - On success: admin sees the expanded
setup.webhookUrlTemplateplussetup.instructions. Pastes URL into provider, copies signing secret back into the form. - Dashboard saves the connection. Subsequent webhook deliveries are routed to the connector’s webhook handler.
There’s no secondary configuration step — once the connection exists, the connector is live.
OAuth setup flow, end to end
- Admin clicks “Install Shopify” in the connectors gallery.
- Dashboard renders required pre-OAuth settings from
connection.settingsSchema, such asshop_domain. - Admin clicks “Connect”. The host starts OAuth using
connection.oauth, creates one-time state, resolves the OAuth client credential, validates dynamic hosts, and redirects to the provider. - Provider redirects to Backfill’s global connector OAuth callback.
- The host verifies state, exchanges the code, stores tokens encrypted, and runs
connection.oauth.postConnectif configured. - Dashboard shows connected provider account metadata, scopes, expiry, and reconnect/disconnect actions.
- Sync routes use
OAuth.accessToken()orHttpwithauth: { oauth: true }.
Do not ask users to paste OAuth access tokens into settingsSchema. Manual token fields are acceptable for local dogfood and route tests only.