Skip to content

Authoring Components

A component is a Git repository (or subdirectory) with this layout:

my-component/
warmhub/
component.json # Identity
manifest.json # Resource declarations

The warmhub/ directory is required. If your component reacts to subscriptions, the webhook handlers themselves live wherever you deploy them; the manifest only stores their URLs.

{
"id": "com.example.MyComponent",
"name": "my-component",
"version": "1.0.0",
"description": "Watches for Foo things and processes them"
}

The id uses reverse-DNS format: lowercase domain segments followed by PascalCase name segments. This is the ownership key — it must be globally unique.

Start with the skeleton and add sections as needed:

{
"$schema": "https://docs.warmhub.ai/schema/component-manifest.v1.json",
"component": {
"id": "com.example.MyComponent",
"name": "my-component",
"version": "1.0.0"
},
"shapes": [],
"credentials": [],
"subscriptions": [],
"seeds": [],
"health": {},
"teardown": {}
}

The component section must match component.json. The $schema field enables IDE autocomplete.

Declare shapes for data your component creates or consumes:

"shapes": [
{ "name": "FooInput", "fields": { "url": "string", "priority": "number" } },
{ "name": "FooResult", "fields": { "summary": "string", "score": "number" } }
]

If your component only reacts to shapes that already exist in the target repo (created by another component or manually), you don’t need to declare them here.

Wire webhook subscriptions. Components declare event-triggered webhook subscriptions:

"subscriptions": [
{
"name": "mc/on-foo-add",
"trigger": { "kind": "event", "shape": "FooInput" },
"kind": "webhook",
"webhookUrl": "https://handler.example.com/foo",
"credentials": ["my-creds"]
}
]

Convention: prefix subscription names with a short component abbreviation (e.g., mc/ for my-component).

Create initial data at install time:

"seeds": [
{
"kind": "thing",
"shape": "ComponentConfig",
"name": "my-component",
"data": { "version": "1.0.0", "enabled": true }
}
]

ComponentConfig is a built-in shape — you don’t need to declare it in shapes.

"health": {
"requires": {
"shapes": ["FooInput", "FooResult"],
"subscriptions": ["mc/on-foo-add"]
}
},
"teardown": {
"subscriptions": { "onDisable": "pause" }
}

wh component doctor validates the shapes, subscriptions, credentials, and seeds declared in the manifest.

Component subscriptions post to webhook URLs that you operate; they do not run local action scripts or action-container runtimes.

Your handler should accept the webhook payload and then use the CLI, SDK, or HTTP API to write results back to WarmHub.

A minimal handler contract looks like this:

  • Read event, runId, repo, and matchedOperations from the POST body.
  • Start any long-running work asynchronously if needed.
  • Use callback_url to report processing, success, failure, or retry_requested for asynchronous work.
  • Authenticate write-back calls with your own token.
Terminal window
wh component validate .

Fix any errors before publishing. Common issues:

  • Missing webhookUrl on a declared subscription
  • Invalid trigger definitions (for example, an event trigger without shape)
  • Component ID format (must be reverse-DNS)
  • Mismatched id/name between component.json and manifest.json

Components install by identity, so register the manifest first, then install by <org>/<name>:

Terminal window
# Register the component identity from its manifest
wh component register my-component --org myorg --manifest ./warmhub/manifest.json
# Install it into a repo
wh component install myorg/my-component --repo myorg/myrepo
# Set any required credentials
wh credential set my-creds openai_key --value sk-...
# Check health
wh component doctor my-component --repo myorg/myrepo
# Trigger by creating data
wh commit submit --add test-input --shape FooInput \
--data '{"url": "https://example.com", "priority": 1}' \
--repo myorg/myrepo
# Check subscription logs
wh sub log mc/on-foo-add --repo myorg/myrepo
  • Avoid self-triggering: If your webhook writes to the same shape the subscription monitors, it will create an infinite loop. Write to a different output shape or tighten the filter.
  • Keep handlers idempotent: WarmHub retries failed deliveries. Use X-WarmHub-Idempotency-Key or runId to deduplicate side effects.
  • Prefix subscription names: Use a consistent prefix (e.g., rk/ for research-knowledge) to avoid collisions with other components.
  • Test with wh sub attempts: After triggering, check delivery status with wh sub attempts <runId> to see exit codes and error categories.