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://warmhub.dev/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 or cron subscriptions. Components can declare event-triggered webhook subscriptions and cron-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, cron without cronspec)
  • Component ID format (must be reverse-DNS)
  • Mismatched id/name between component.json and manifest.json
Terminal window
# If your component has no subscriptions, install from a local path for development
wh component install . --repo myorg/myrepo
# If your component declares subscriptions, install from a GitHub source instead
wh component install github.com/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.