# Triggering Deployed Workflows
Source: https://docs.chain.link/cre/guides/workflow/using-triggers/http-trigger/triggering-deployed-workflows
Last Updated: 2026-11-02

> For the complete documentation index, see [llms.txt](/llms.txt).

Once you've [deployed a workflow](/cre/guides/operations/deploying-workflows) with an HTTP trigger, you can execute it by sending authenticated HTTP requests to the CRE gateway. This guide explains how to trigger deployed workflows in production.

> **NOTE: One HTTP trigger per workflow**
>
> Each workflow supports only one HTTP trigger handler. There is currently no mechanism to route requests to different
> HTTP trigger handlers within the same workflow. If you need to handle different use cases or execution paths, include
> routing logic in your request payload and branch within a single handler function.

## What you'll learn

This guide covers the complete technical specification for triggering deployed workflows:

- **Request format** - The JSON-RPC structure for workflow execution requests
- **JWT authentication** - How to generate cryptographically signed tokens
- **Signature process** - The ECDSA signing steps for Ethereum-compatible authentication
- **Reference implementations** - Code examples in Go and TypeScript

> **NOTE: Just testing?**
>
> If you're testing deployed workflows during development, the [Local Testing
> Tool](/cre/guides/workflow/using-triggers/http-trigger/local-testing-tool) is much simpler—it handles all JWT
> generation automatically. This guide is for production implementations where you need full control over the HTTP
> requests.

## Prerequisites

- **Deployed workflow**: Your workflow must be deployed with an HTTP trigger. See [Deploying Workflows](/cre/guides/operations/deploying-workflows).
- **Workflow ID**: Available from deployment output or the [CRE UI](/cre/guides/operations/monitoring-workflows).
- **Private key**: The private key corresponding to one of the `authorizedKeys` configured in your HTTP trigger.

> **NOTE: For easier testing**
>
> If you're testing your deployed workflow during development, consider using the [Local Testing
> Tool](/cre/guides/workflow/using-triggers/http-trigger/local-testing-tool) instead. It handles JWT generation
> automatically and triggers the workflow for you.

## Finding your workflow ID

Your workflow ID is a 64-character hexadecimal string (without `0x` prefix) that uniquely identifies your deployed workflow.

### From deployment output

When you deploy a workflow, the CLI displays the workflow ID:

```bash
$ cre workflow deploy my-workflow --target production-settings

...
Details:
   Workflow ID:    a1b2c3d4e5f67890a1b2c3d4e5f67890a1b2c3d4e5f67890a1b2c3d4e5f67890
...
```

### From the CRE UI

1. Log in to [app.chain.link/cre/workflows](https://app.chain.link/cre/workflows)
2. Click on your workflow name
3. In the **Overview** section, find the **Workflow ID** field
4. Click the copy button to copy it to your clipboard

## Gateway URLs

Find your production HTTP trigger endpoint in the UI under [Endpoints](https://app.chain.link/organization?otab=configuration). To see your environment, run `cre whoami` in the CLI.

Here are the trigger endpoints per environment:

| Environment    | Trigger endpoint                                       |
| -------------- | ------------------------------------------------------ |
| CRE Public     | `https://01.gateway.zone-a.cre.chain.link`             |
| CRE Enterprise | `https://01.enterprise-gateway.zone-a.cre.chain.link/` |

## Request format

All workflow executions use JSON-RPC 2.0 format:

```http
POST https://01.gateway.zone-a.cre.chain.link
Content-Type: application/json
Authorization: Bearer <JWT_TOKEN>

{
  "id": "unique-request-id",
  "jsonrpc": "2.0",
  "method": "workflows.execute",
  "params": {
    "input": {
      "key1": "value1",
      "key2": "value2"
    },
    "workflow": {
      "workflowID": "your-64-character-workflow-id"
    }
  }
}
```

### Request components

| Field                        | Description                                                        |
| ---------------------------- | ------------------------------------------------------------------ |
| `jsonrpc`                    | Always `"2.0"` (JSON-RPC version)                                  |
| `id`                         | Unique identifier for this request (any string, used for tracking) |
| `method`                     | Always `"workflows.execute"`                                       |
| `params.input`               | Your custom JSON payload (passed to your workflow callback)        |
| `params.workflow.workflowID` | Your 64-character workflow ID (no `0x` prefix)                     |

> **TIP: Request ID for tracking**
>
> The `id` field helps you correlate requests with responses. Use a UUID or timestamp-based identifier for easier
> debugging and request tracking.

## JWT authentication

The `Authorization` header must contain a Bearer JWT (JSON Web Token) that proves the request was signed by an authorized key. The JWT has three parts: `header.payload.signature`.

### JWT structure

The JWT is a base64url-encoded string consisting of three parts separated by dots:

```
<base64url(header)>.<base64url(payload)>.<base64url(signature)>
```

### 1. Header

The JWT header specifies the signing algorithm:

```json
{
  "alg": "ETH",
  "typ": "JWT"
}
```

Base64url-encode this JSON to create the header part.

### 2. Payload

The JWT payload contains request metadata and a digest of the request body:

```json
{
  "digest": "0x<sha256_hash_of_request_body>",
  "iss": "0xYourEVMAddress",
  "iat": 1762807282,
  "exp": 1762807582,
  "jti": "550e8400-e29b-41d4-a716-446655440000"
}
```

**Payload fields:**

| Field    | Description                                                                  |
| -------- | ---------------------------------------------------------------------------- |
| `digest` | SHA256 hash of the JSON-RPC request body (with `0x` prefix)                  |
| `iss`    | Issuer - your EVM address (the public key corresponding to your private key) |
| `iat`    | Issued at time (Unix timestamp in seconds)                                   |
| `exp`    | Expiration time (Unix timestamp, **max 5 minutes** after `iat`)              |
| `jti`    | JWT ID (UUID v4 for replay protection)                                       |

#### Computing the digest

The `digest` is a SHA256 hash of your JSON-RPC request body **serialized as UTF-8 encoded JSON in ascending lexicographic order** (sorted by key names):

**Original request:**

```json
{
  "jsonrpc": "2.0",
  "id": "req-123",
  "method": "workflows.execute",
  "params": {
    "input": { "key1": "value1", "key2": "value2" },
    "workflow": { "workflowID": "a1b2c3..." }
  }
}
```

**Keys must be sorted alphabetically at every level:**

```json
{
  "id": "req-123",
  "jsonrpc": "2.0",
  "method": "workflows.execute",
  "params": { "input": { "key1": "value1", "key2": "value2" }, "workflow": { "workflowID": "a1b2c3..." } }
}
```

Then compute: `digest = "0x" + sha256(sorted_json_string)`

> **CAUTION: Key ordering is critical**
>
> The digest **must** be computed from a JSON string with keys sorted in ascending lexicographic order at all nesting
> levels. Incorrect ordering will cause signature verification to fail.

### 3. Signature

The signature is an ECDSA signature of the message `<base64url(header)>.<base64url(payload)>` using your private key.

**Signing process:**

1. Concatenate the encoded header and payload: `message = base64url(header) + "." + base64url(payload)`
2. Sign the message using ECDSA with your private key:
   - Prepend the Ethereum signed message prefix: `"\x19Ethereum Signed Message:\n" + len(message) + message`
   - Hash the prefixed message with Keccak256
   - Sign the hash using your private key
3. Extract the signature components: `r` (32 bytes), `s` (32 bytes), `v` (1 byte, recovery ID)
4. Concatenate: `signature_bytes = r || s || v`
5. Base64url-encode the signature bytes

## Reference implementations

Manual JWT generation requires careful cryptographic operations. Use these reference implementations as guidance:

### For Go

Production-grade utilities:

- **JWT creation**: [jwt.go](https://github.com/smartcontractkit/chainlink/blob/develop/core/utils/jwt.go) - See the `CreateRequestJWT` method for complete JWT generation
- **ECDSA signatures**: [eth\_signatures.go](https://github.com/smartcontractkit/chainlink/blob/develop/core/utils/eth_signatures.go) - See the `GenerateEthSignature` method for Ethereum-compatible signing

### For TypeScript/JavaScript

The CRE SDK includes a reference implementation using `viem`:

- **Complete implementation**: [cre-http-trigger source code](https://github.com/smartcontractkit/cre-sdk-typescript/tree/main/packages/cre-http-trigger/src)
- Key files:
  - `create-jwt.ts` - JWT header, payload, and signing logic
  - `utils.ts` - SHA256 hashing and base64url encoding helpers

### For testing

If you're testing deployed workflows during development, use the [Local Testing Tool](/cre/guides/workflow/using-triggers/http-trigger/local-testing-tool) which runs a local proxy server that handles the entire JWT generation and request flow automatically.

## Example request (conceptual)

Here's what a complete curl request looks like (using the public DON gateway — see [Gateway URLs](#gateway-urls) to pick the right endpoint for your workflow's execution environment):

```bash
curl -X POST https://01.gateway.zone-a.cre.chain.link \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <JWT_TOKEN>" \
  -d '{
    "jsonrpc": "2.0",
    "id": "req-123",
    "method": "workflows.execute",
    "params": {
      "input": {
        "userId": "user_123",
        "action": "purchase",
        "amount": 100
      },
      "workflow": {
        "workflowID": "a1b2c3d4e5f67890a1b2c3d4e5f67890a1b2c3d4e5f67890a1b2c3d4e5f67890"
      }
    }
  }'
```

## Response format

### Success response

When your request is successfully accepted, the gateway returns a JSON-RPC response:

```json
{
  "jsonrpc": "2.0",
  "id": "req-123",
  "method": "workflows.execute",
  "result": {
    "workflow_id": "<your-workflow-id>",
    "workflow_execution_id": "<your-workflow-execution-id>",
    "status": "ACCEPTED"
  }
}
```

**Response fields:**

| Field                          | Description                                                                                                 |
| ------------------------------ | ----------------------------------------------------------------------------------------------------------- |
| `jsonrpc`                      | JSON-RPC version (always `"2.0"`)                                                                           |
| `id`                           | The request ID you provided in the request                                                                  |
| `method`                       | The method called (always `"workflows.execute"`)                                                            |
| `result.workflow_id`           | Your workflow ID (with `0x` prefix)                                                                         |
| `result.workflow_execution_id` | Unique execution ID for this workflow run (use this to track execution in the CRE UI)                       |
| `result.status`                | Execution status (typically `"ACCEPTED"` when the workflow trigger is successfully accepted by the gateway) |

> **TIP: Track your execution**
>
> Copy the `workflow_execution_id` to track this specific execution in the [CRE
> UI](https://app.chain.link/cre/workflows). You can view logs, events, and execution details using this ID.

### Error response

If the request fails (e.g., invalid JWT, unauthorized key, workflow not found), the gateway returns an error response. Example:

```json
{
  "jsonrpc": "2.0",
  "id": "req-123",
  "method": "",
  "error": {
    "code": -32600,
    "message": "Auth failure: signer '0x...' is not authorized for workflow '0x...'. Ensure that the signer is registered in the workflow definition"
  }
}
```

> **NOTE: HTTP status codes**
>
> Most error responses return HTTP status `400 Bad Request`. The JSON-RPC error object provides detailed information
> about what went wrong. Rate limit errors are the exception and return `429 Too Many Requests`; see [Rate limit
> error](#rate-limit-error).

### Rate limit error

If a workflow exceeds its configured HTTP trigger rate limit, the gateway rejects the request before dispatching it to
the DON. The response uses HTTP status `429 Too Many Requests` with JSON-RPC error code `-32002`:

```http
HTTP/1.1 429 Too Many Requests
Content-Type: application/json

{
  "jsonrpc": "2.0",
  "id": "req-123",
  "method": "",
  "error": {
    "code": -32002,
    "message": "rate limited"
  }
}
```

When you receive a `429`:

- **The request was not executed and was not queued.** No workflow run was started for it.
- **The limit is applied per workflow.** The configured HTTP trigger quota is documented in
  [Service Quotas](/cre/service-quotas#http-trigger). Limits can be raised for a workflow, owner, or organization.
- **No retry hint is returned.** The response does not include `Retry-After` or `X-RateLimit-*` headers, so your
  client must decide when to retry.
- **Only rate limit errors use `429`.** Other `4xx` responses (for example, `-32600` auth failures or `-32700` parse
  errors) indicate a problem with the request itself. Fix the request instead of retrying it unchanged.

### Backoff and retry

Because the gateway does not return a `Retry-After` value, implement exponential backoff with jitter:

1. Treat HTTP `429` as retryable. Do not blindly retry other `4xx` responses.
2. Wait before retrying: start with a short delay (for example, one second), double it on each attempt, and add random
   jitter so many clients do not retry simultaneously. Cap the delay and the total number of attempts.
3. Generate a **new JWT for every retry**. The `jti` must be new, and `iat`/`exp` must reflect the retry time. If you
   change the request body, recompute the `digest` as well.
4. If retries are exhausted, surface the `429` to your caller and log it. Do not keep retrying indefinitely.

A `429` means the request was rejected before execution, so retrying it does not double-execute the workflow.

> **NOTE: Local simulation behaves differently**
>
> `cre workflow simulate --listen` enforces the same HTTP trigger quota differently: it skips rate-limited requests and
> still returns `200 OK`, printing `Trigger rate limited, skipping execution` in the terminal. Do not build your
> production retry logic on the simulator's `200` response. See [Simulating
> Workflows](/cre/guides/operations/simulating-workflows#listen-mode) and [Testing Production
> Limits](/cre/guides/operations/understanding-limits).

## Verifying execution

After triggering your workflow, verify execution in the CRE UI:

1. Go to [app.chain.link/cre/workflows](https://app.chain.link/cre/workflows)
2. Click on your workflow
3. Check the **Execution** tab for recent runs
4. Click on an **Execution ID** to view detailed logs and events

See [Monitoring & Debugging Workflows](/cre/guides/operations/monitoring-workflows) for complete monitoring guidance.

## Security considerations

### Private key protection

- **Never commit private keys** to version control
- **Use environment variables** or secret management tools (e.g., AWS Secrets Manager, HashiCorp Vault)
- **Rotate keys periodically** and update your workflow's `authorizedKeys` if compromised

### Request expiration

- JWT tokens expire after the `exp` timestamp (max 5 minutes after `iat`)
- This prevents replay attacks with captured requests
- Generate new JWTs for each request

### Replay protection

- The `jti` (JWT ID) field provides replay protection
- Use a unique UUID for every request
- The gateway may reject duplicate `jti` values within the expiration window

## Next steps

### For easier testing

Manual JWT generation is complex and error-prone. For development and testing:

- **[Local Testing Tool](/cre/guides/workflow/using-triggers/http-trigger/local-testing-tool)** - Automatically generates JWTs and sends requests

### Additional resources

- **[HTTP Trigger SDK Reference](/cre/reference/sdk/triggers/http-trigger)** - Complete API documentation
- **[Monitoring Workflows](/cre/guides/operations/monitoring-workflows)** - Track execution history and debug issues