> ## Documentation Index
> Fetch the complete documentation index at: https://docs.goldsky.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Task Triggers

Your compose tasks can be triggered by several different mechanisms. Triggers are configured in your [manifest file](./app-configuration) for each task.
Each task can have multiple triggers (one of each type), so triggers are configured as an array. Below is an overview of all the supported trigger types: `cron`, `http`, and `onchain_event`.

## Local Task Execution

For locally testing tasks you can use the `callTask` CLI command. This works regardless of what trigger types a task has configured (and even for tasks with no triggers at all).

#### Trigger Task locally with CLI

```bash theme={null}
goldsky compose callTask "my_task" '{ "foo": "bar" }'
```

#### Task Code

```typescript theme={null}
import { TaskContext } from "compose";

type Payload = {
  foo: string;
};

export async function main({ collection }: TaskContext, payload: Payload) {
  // the task will be called with the payload sent from the CLI callTask command
  console.log(payload.foo);
}
```

## Task To Task Execution

Tasks can call other tasks directly — no trigger configuration required on the called task. This is done using the [callTask](./context/call-task) context function.

### Example

```typescript highlight={10-17} theme={null}
import { TaskContext } from "compose";

type ProcessDataPayload = {
  dataId: string;
  operation: string;
};

export async function main({ callTask }: TaskContext, payload: ProcessDataPayload) {
  // Call another task with a payload
  const result = await callTask<{ success: boolean; processed: number }>(
    "process-data",
    {
      dataId: payload.dataId,
      operation: payload.operation,
    }
  );

  return {
    status: "completed",
    result,
  };
}
```

The `process-data` task will receive the payload and can return a response:

```typescript theme={null}
import { TaskContext } from "compose";

type ProcessDataArgs = {
  dataId: string;
  operation: string;
};

export async function main({ collection }: TaskContext, payload: ProcessDataArgs) {
  // Process the data based on the operation
  const processed = await collection("data").findMany({ id: payload.dataId });

  return {
    success: true,
    processed: processed.length,
  };
}
```

## Chain Event Triggers

Tasks can be triggered by onchain events. The payload delivered to the task is the raw encoded log. Compose provides helpers for decoding the event in your task code — see [Contracts](./context/evm/contracts) for more details on decoding.

<Warning>
  Onchain event triggers only fire when your app is **deployed** to the cloud. They do not fire during local development.
  To test a task with an onchain trigger locally, use `callTask` with a sample event payload:

  ```bash theme={null}
  goldsky compose callTask "my_task" '{"blockNumber":48758053,"address":"0xb74de3F91e04d0920ff26Ac28956272E8d67404D","data":"0x","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"],"transactionHash":"0x2c50...","logIndex":343}'
  ```

  You can copy a real event payload from a block explorer to use as test data.
</Warning>

#### Example event object sent as the payload to your task:

```json theme={null}
{
  "blockNumber": 48758053,
  "blockHash":
    "0x6794a56583329794f184d50862019ecf7b6d8ba6b3210f68ca4b91a8fa81817d",
  "transactionIndex": 29,
  "removed": false,
  "address": "0xb74de3F91e04d0920ff26Ac28956272E8d67404D",
  "data": "0x",
  "topics": [
    "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
    "0x0000000000000000000000000000000000000000000000000000000000000000",
    "0x000000000000000000000000eec2ba9b9f0202c63bba29ea9a4ce5c23f9865fd",
    "0x0000000000000000000000000000000000000000000000000000000000001099",
  ],
  "transactionHash":
    "0x2c500a55f5c24d587e73805975d91395634a971dca5939f43d34d774d0f7147b",
  "logIndex": 343,
}
```

#### Example configuration for an onchain event trigger:

```yaml highlight={6-10} theme={null}
name: "my-app"
tasks:
  - name: "on_transfer"
    path: "./tasks/on_transfer.ts"
    triggers:
      - type: "onchain_event"
        network: "base_sepolia"
        contract: "0xb74de3F91e04d0920ff26Ac28956272E8d67404D"
        events:
          - "Transfer(address,address,uint256)"
```

#### Task code

Here's an example in which we decode the event with a contract class we've generated with `goldsky compose codegen`, see [Contracts](./context/evm/contracts) for more info.

```typescript theme={null}
import { TaskContext, OnchainEvent } from "compose";

// the payload for an onchain triggered task will always be our built in OnchainEvent interface
export async function main({ evm }: TaskContext, payload: OnchainEvent) {
  // we can decode the event with our ABI generated contract class
  // the type of this decodedEvent will be a union of all events that the ABI specifies, allowing you to conditionally process based on the eventName
  const decodedEvent = evm.contracts.MyNFT.decodeEventLog(payload);

  if (decodedEvent.eventName === "Transfer") {
    // in this code block typescript will know the decodedEvent is of type TransferEventDecoded
    // this pattern is useful when your trigger supports multiple event types
    console.log("Transfer event:", decodedEvent.args);
  }

  // alternative would be to cast, this is useful if your trigger is configured for a single event type
  const castEvent = evm.contracts.MyNFT.decodeEventLog<TransferEventDecoded>(payload);
}
```

#### Onchain Trigger Properties

| Property          | Type              | Required | Description                                                                                                                                                          |
| ----------------- | ----------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `type`            | `"onchain_event"` | Yes      | Type discriminator                                                                                                                                                   |
| `network`         | string            | Yes      | The network slug in snake\_case (e.g. `base_sepolia`, `ethereum_mainnet`). See [Chains](./context/evm/chains) for the full list.                                     |
| `contract`        | string            | Yes      | The contract address (`0x` followed by 40 hex characters).                                                                                                           |
| `events`          | string\[]         | No       | Specific event signatures (human-readable, not encoded) to match — e.g. `"Transfer(address,address,uint256)"`. If omitted, all logs from the contract are delivered. |
| `dataset_version` | string            | No       | Override the dataset version for chains that don't use `1.0.0` (e.g., `megaeth_testnet_v2`). If not specified, the server auto-detects the correct version.          |

#### Failure Handling

Onchain-triggered tasks honor the task's [`retry_config`](./app-configuration#retry-configuration). When a task throws (or otherwise fails), Compose retries it up to `max_attempts` with the configured backoff. If every attempt fails, **that event is dropped and the pipeline advances to the next one** — it is not replayed.

This means an unhandled exception in an onchain-triggered task only stalls delivery for the duration of your retry window, not indefinitely. If you want a failure to be visible without consuming retries, catch the error in your task and return normally — the run is recorded as successful and the pipeline moves on.

## Cron Triggers

Tasks can be triggered on a schedule. Tasks triggered by cron are invoked with an empty payload (`{}`), since each invocation is generic.

### Example

```yaml highlight={6-7} theme={null}
name: "my-app"
tasks:
  - name: "hourly_sync"
    path: "./tasks/sync.ts"
    triggers: 
      - type: "cron"
        expression: "0 * * * *" # Every hour at minute 0
```

### 6-field (second-granularity) example

Pass a 6-field expression if you need second-level precision — the first field becomes seconds:

```yaml theme={null}
triggers:
  - type: "cron"
    expression: "*/30 * * * * *" # every 30 seconds
```

#### Cron Trigger Properties

| Property     | Type     | Required | Description                                                                                                                                                                                                              |
| ------------ | -------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `type`       | `"cron"` | Yes      | Type discriminator                                                                                                                                                                                                       |
| `expression` | string   | Yes      | A cron expression. Supports both 5-field (`minute hour day month weekday`) and 6-field (`second minute hour day month weekday`) formats. Accepts `*`, numbers, ranges (`1-5`), lists (`1,3,5`), and step values (`*/2`). |

## HTTP Triggers

Tasks can be triggered by HTTP requests. This is often used to kick off compose tasks from your application logic. HTTP triggers accept any JSON payload you want for dynamic execution.

### Example

```yaml highlight={6-7} theme={null}
name: "my-app"
tasks:
  - name: "status"
    path: "./tasks/status.ts"
    triggers:
      - type: "http"
        authentication: "auth_token" # other option is "none"
```

#### Http Trigger Properties

| Property         | Type                                         | Required | Description                                                                                                                                           |
| ---------------- | -------------------------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| `type`           | `"http"`                                     | Yes      | Type discriminator                                                                                                                                    |
| `authentication` | `"auth_token" \| "none"`                     | Yes      | Type of authentication to use on the endpoint.                                                                                                        |
| `role`           | `"Owner" \| "Admin" \| "Editor" \| "Viewer"` | No       | The minimum RBAC role required to call the endpoint (only applies when `authentication` is `"auth_token"`). Defaults to `"Viewer"` (any team member). |
| `ip_whitelist`   | string\[]                                    | No       | Restrict access to specific IPs. Supports IPv4, IPv6, and CIDR notation (e.g. `["192.168.1.0/24", "10.0.0.1"]`).                                      |

Authenticated triggers require a Goldsky API token when the app is deployed, but can be called without auth when testing locally. Unauthenticated triggers can be called without auth both locally and when deployed.

### Base URL

Locally, Compose runs on `http://localhost:4000`. The base pattern for all task endpoints is:

```
http://localhost:4000/tasks/{taskName}
```

When deployed to the cloud, tasks with an authenticated HTTP trigger (`authentication: "auth_token"`) are reachable at:

```
https://api.goldsky.com/api/admin/compose/v1/{appName}/tasks/{taskName}
```

When deployed to the cloud, tasks with an unauthenticated HTTP trigger (`authentication: "none"`) are reachable at:

```
https://api.goldsky.com/api/public/compose/v1/{projectId}/{appName}/tasks/{taskName}
```

### HTTP Request Format

```bash theme={null}
curl -X POST http://localhost:4000/tasks/{taskName} \
  -H "Content-Type: application/json" \
  -d '{"key": "value"}'
```

When deployed to the cloud with `authentication: "auth_token"`, you'll need to pass a Goldsky API token:

```bash highlight={2} theme={null}
curl -X POST https://api.goldsky.com/api/admin/compose/v1/{appName}/tasks/{taskName} \
  -H "Authorization: Bearer {token}" \
  -H "Content-Type: application/json" \
  -d '{"foo": "foo"}'
```

### Execute HTTP trigger without parameters

```bash highlight={3} theme={null}
curl -X POST http://localhost:4000/tasks/bitcoin-oracle \
  -H "Content-Type: application/json" \
  -d '{}'
```

### Execute an HTTP trigger with parameters

```bash highlight={4-5} theme={null}
curl -X POST http://localhost:4000/tasks/bitcoin-oracle \
  -H "Content-Type: application/json" \
  -d '{
    "contractAddress": "0x742d35Cc6634C0532925a3b8D6Ac6E7D9C3c1234",
    "threshold": 1000
  }'
```

### Response Format

Tasks return JSON responses with the data returned by the task's `main` function.

Calling this task:

```typescript theme={null}
import { TaskContext } from "compose";

export async function main(_context: TaskContext, payload: { name?: string }) {
  const name = payload?.name || "World";

  return { success: true, greeting: `Hello, ${name}!` };
}
```

via this request:

```bash theme={null}
curl -X POST http://localhost:4000/tasks/greeting \
  -H "Content-Type: application/json" \
  -d '{
    "name":"Adam"
  }'
```

returns this JSON response:

```json theme={null}
{
  "success": true,
  "greeting": "Hello, Adam!"
}
```

## Next Steps

<CardGroup cols={2}>
  <Card title="Using Packages" icon="file-code" href="./packages">
    You can use any sandbox compatible typescript packages with any package manager.
  </Card>

  <Card title="Debugging" icon="code" href="./debugging">
    Debug and monitor your apps
  </Card>
</CardGroup>
