Skip to content

2026-01-29

Connector SDK: Build Your Own Integration

Intended Team · Founding Team

Why Custom Connectors

Intended ships connectors for GitHub, GitLab, Jira, Salesforce, ServiceNow, AWS, and Kubernetes. These cover the most common systems that AI agents interact with. But every organization has systems that are not on that list: internal APIs, proprietary platforms, legacy systems, and custom tools.

If your AI agents interact with a system that Intended does not have a built-in connector for, you need a custom connector. The Connector SDK makes this straightforward. A basic connector can be built in under 200 lines of code.

What a Connector Does

A connector serves as the bridge between an external system and Intended's governance pipeline. It has three responsibilities.

First, it receives events from the external system. This might be a webhook, a polling mechanism, or a direct API call from the agent.

Second, it normalizes the event into Intended's unified intent format. The external system sends data in its own format. The connector transforms it into the structured intent format that the Authority Engine understands.

Third, it handles execution-time token verification. When an agent presents an authority token to execute an action, the connector verifies the token with Intended before allowing the action to proceed.

Setting Up the SDK

The Connector SDK is available as an npm package. Install it in your project:

bash
npm install @intended/connector-sdk

The SDK provides a base class, type definitions, and utilities for building connectors. You extend the base class and implement the system-specific logic.

Step 1: Define the Connector

Create a new file for your connector. We will build a connector for a hypothetical internal deployment system called "Shipyard."

typescript
import {
  BaseConnector,
  ConnectorConfig,
  RawEvent,
  NormalizedIntent,
} from "@intended/connector-sdk";

const config: ConnectorConfig = {
  id: "shipyard",
  name: "Shipyard Deployment System",
  version: "1.0.0",
  supportedEvents: [
    "deployment.requested",
    "rollback.requested",
    "config.changed",
  ],
};

export class ShipyardConnector extends BaseConnector {
  constructor() {
    super(config);
  }
}

The `ConnectorConfig` tells Intended about your connector: its identifier, human-readable name, version, and the event types it handles. The `supportedEvents` array lists the event types that your system sends.

Step 2: Implement Signature Validation

Every connector must validate that incoming events are authentic. This prevents attackers from sending fake events to trigger governance decisions.

Most systems use HMAC signatures for webhook validation. Your system might use JWT tokens, API keys, or another mechanism. Implement the `validateSignature` method:

typescript
import { createHmac } from "crypto";

export class ShipyardConnector extends BaseConnector {
  constructor() {
    super(config);
  }

  async validateSignature(
    payload: string,
    headers: Record<string, string>
  ): Promise<boolean> {
    const signature = headers["x-shipyard-signature"];
    if (!signature) return false;

    const secret = this.getSecret("SHIPYARD_WEBHOOK_SECRET");
    const computed = createHmac("sha256", secret)
      .update(payload)
      .digest("hex");

    return `sha256=${computed}` === signature;
  }
}

The `getSecret` method retrieves the webhook secret from Intended's secret management system. Secrets are never hardcoded in connector code.

Step 3: Implement Event Parsing

The parser transforms the raw webhook payload into an intermediate representation. This is where you handle your system's specific data format.

typescript
interface ShipyardEvent {
  eventType: string;
  service: string;
  environment: string;
  version: string;
  triggeredBy: string;
  timestamp: string;
  metadata: Record<string, unknown>;
}

export class ShipyardConnector extends BaseConnector {
  // ... previous methods

  async parseEvent(raw: RawEvent): Promise<ShipyardEvent> {
    const body = JSON.parse(raw.body);
    return {
      eventType: body.event_type,
      service: body.service_name,
      environment: body.env,
      version: body.deploy_version || body.config_version,
      triggeredBy: body.actor.username,
      timestamp: body.created_at,
      metadata: body.extra || {},
    };
  }
}

The parser handles any quirks in your system's data format: nested objects, optional fields, different field names across event types. The output is a clean, consistent intermediate representation.

Step 4: Implement Intent Mapping

The mapper transforms the parsed event into Intended's unified intent format. This is the core of the connector: the translation from your system's vocabulary to Intended's governance vocabulary.

typescript
export class ShipyardConnector extends BaseConnector {
  // ... previous methods

  async mapToIntent(event: ShipyardEvent): Promise<NormalizedIntent> {
    const actionMap: Record<string, string> = {
      "deployment.requested": "sdlc.deployment.service.deploy",
      "rollback.requested": "sdlc.deployment.service.rollback",
      "config.changed": "infrastructure.config.service.update",
    };

    const action = actionMap[event.eventType];
    if (!action) {
      throw new UnknownEventError(event.eventType);
    }

    const [domain, category] = this.parseDomainCategory(action);

    return {
      domain,
      category,
      action,
      resource: {
        type: "service",
        identifier: event.service,
        environment: event.environment,
      },
      agent: {
        externalId: event.triggeredBy,
      },
      parameters: {
        version: event.version,
        ...event.metadata,
      },
      timestamp: event.timestamp,
      source: "shipyard",
    };
  }
}

The action map translates your system's event types to MIR action types. The `resource` object identifies what the action targets. The `agent` object identifies who (or what) triggered the action. The `parameters` object includes any additional data relevant to governance evaluation.

Step 5: Register and Deploy

With the three methods implemented, register your connector with Intended:

typescript
import { registerConnector } from "@intended/connector-sdk";
import { ShipyardConnector } from "./shipyard-connector";

registerConnector(new ShipyardConnector());

The `registerConnector` function sets up the webhook endpoint, configures the health check, and starts listening for events. The SDK handles the HTTP server, request routing, error handling, and retry logic.

Deploy the connector as a standalone service (a container or serverless function) or embed it in your existing application. The connector communicates with Intended's API to submit intents and verify tokens.

Step 6: Configure in Intended Console

After deploying the connector, register it in the Intended console. Provide the connector's endpoint URL, the webhook secret (stored encrypted in Intended's secret management), and the event-to-domain mapping. Intended will send a test event to verify connectivity and normalization.

Testing Your Connector

The SDK includes a test harness for validating your connector without connecting to a live Intended instance.

typescript
import { ConnectorTestHarness } from "@intended/connector-sdk/testing";
import { ShipyardConnector } from "./shipyard-connector";

const harness = new ConnectorTestHarness(new ShipyardConnector());

test("normalizes deployment event", async () => {
  const raw = {
    body: JSON.stringify({
      event_type: "deployment.requested",
      service_name: "api-gateway",
      env: "production",
      deploy_version: "2.4.1",
      actor: { username: "deploy-bot" },
      created_at: "2026-02-01T10:00:00Z",
    }),
    headers: { "x-shipyard-signature": "sha256=valid" },
  };

  const intent = await harness.processEvent(raw);
  expect(intent.action).toBe("sdlc.deployment.service.deploy");
  expect(intent.resource.environment).toBe("production");
  expect(intent.resource.identifier).toBe("api-gateway");
});

The test harness validates signature checking, event parsing, and intent mapping without network calls. Run these tests in your CI pipeline to ensure connector correctness.

Best Practices

Map events conservatively. If you are unsure which MIR action type an event maps to, choose the more restrictive classification. You can always relax it later. Over-classifying risk is safer than under-classifying it.

Handle unknown events gracefully. Your system might add new event types that your connector does not know about. The `UnknownEventError` routes these to an error queue for investigation rather than dropping them silently.

Version your connector. When your external system changes its webhook format, update the connector and deploy a new version. Use the connector version to track which normalization logic was active for each event in the audit trail.

Test with production-like data. The test harness is useful for unit testing, but also test your connector with real webhook payloads from your system. Capture sample payloads and use them as test fixtures.

The Connector SDK is open-source and available on GitHub. If you build a connector for a system that other organizations might use, consider contributing it back. The more connectors in the ecosystem, the more value for everyone.