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

# HTTP

> Webhook signals for external services (GitHub, Bitbucket, Jira) and programmatic triggers.

HTTP signals receive JSON payloads over POST. The same endpoint serves two use cases:

* **Webhooks from external services** (GitHub, Bitbucket, Jira, etc.) delivered through the bundled tunnel
* **Programmatic triggers** from the CLI, Studio, or API calls directly against Friday

There's no separate "manual" signal type — triggering by hand is just a POST to the HTTP signal's API route.

## Config

| Field     | Type   | Required | Default | Description                                                    |
| --------- | ------ | -------- | ------- | -------------------------------------------------------------- |
| `path`    | string | Yes      | —       | HTTP path for the webhook (method is always POST)              |
| `timeout` | string | No       | —       | Max time before the signal handler gives up (e.g. `30s`, `2m`) |

## Example

```yaml workspace.yml theme={null}
signals:
  new-pr:
    title: "New pull request"
    description: "Fires on GitHub PR events and manual triggers"
    provider: http
    config:
      path: "/pr"
    schema:
      type: object
      properties:
        pr_url: { type: string }
        repo:   { type: string }
```

## Webhook URLs

External services deliver events to the bundled tunnel, which forwards them into Friday. The public URL shape is:

```
https://{tunnel-domain}/hook/{provider}/{workspaceId}/{signalId}
```

* `{tunnel-domain}` — your Cloudflare tunnel host (ephemeral, shown in Studio → **Settings** → **Webhook tunnel**)
* `{provider}` — determines how the incoming payload is transformed before reaching your job
* `{workspaceId}` — the space owning the signal
* `{signalId}` — the signal name from `workspace.yml`

### Built-in providers

| Provider    | Behavior                                                   |
| ----------- | ---------------------------------------------------------- |
| `github`    | Extracts `pr_url` from GitHub pull request events          |
| `bitbucket` | Extracts `pr_url` from Bitbucket pull request events       |
| `jira`      | Extracts `issue_key`, `project_key` from Jira issue events |
| `raw`       | Forwards the payload as-is (no transformation)             |

Configure these URLs in the external service's webhook settings (GitHub repo settings, Bitbucket repository webhooks, Jira automation) to wire them into Friday workflows.

## Webhook mappings

The tunnel uses a `webhook-mappings.yml` file to decide which events to accept and how to extract signal payload fields from incoming webhook bodies. The image ships with sensible defaults for the starter spaces.

Each provider entry defines:

* **`event_header`** or **`event_field`** — where to find the event type (HTTP header for GitHub/Bitbucket, body field for Jira)
* **`signature_header`** — header used for <Tooltip tip="Hash-based Message Authentication Code">HMAC</Tooltip>-SHA256 verification
* **`events`** — map of event names to an optional `actions` filter and a `mapping` of output field to dot-path into the webhook body

<AccordionGroup>
  <Accordion title="Default webhook-mappings.yml">
    ```yaml theme={null}
    providers:
      github:
        event_header: x-github-event
        signature_header: x-hub-signature-256

        events:
          pull_request:
            actions: [opened, reopened, synchronize]
            mapping:
              pr_url: "pull_request.html_url"

          issues:
            actions: [opened, labeled]
            mapping:
              issue_url: "issue.html_url"
              issue_key: "issue.number"
              title: "issue.title"
              action: "action"

          push:
            mapping:
              ref: "ref"
              repo: "repository.full_name"
              sha: "after"
              pusher: "pusher.name"

      bitbucket:
        event_header: x-event-key
        signature_header: x-hub-signature

        events:
          "pullrequest:created":
            mapping:
              pr_url: "pullrequest.links.html.href"

          "pullrequest:updated":
            mapping:
              pr_url: "pullrequest.links.html.href"

          "repo:push":
            mapping:
              repo: "repository.full_name"
              branch: "push.changes[0].new.name"
              sha: "push.changes[0].new.target.hash"

      jira:
        event_field: webhookEvent
        signature_header: x-hub-signature

        events:
          "jira:issue_created":
            mapping:
              issue_key: "issue.key"
              project_key: "issue.fields.project.key"
              summary: "issue.fields.summary"
              repo_url: "issue.fields.customfield_10000"

          "jira:issue_updated":
            mapping:
              issue_key: "issue.key"
              project_key: "issue.fields.project.key"
              summary: "issue.fields.summary"
              repo_url: "issue.fields.customfield_10000"
    ```
  </Accordion>
</AccordionGroup>

### Customizing mappings

Place a custom `webhook-mappings.yml` in the `config/` subdirectory of the Friday home directory (default `~/.friday/local/config/webhook-mappings.yml`). Friday loads it on startup, overriding the built-in defaults.

## Triggering manually

The same signal can be fired directly against Friday — no tunnel, no provider prefix. This is how Studio's *Run* button, `friday signal trigger`, and any API caller invoke HTTP signals.

```bash theme={null}
curl -X POST http://localhost:18080/api/workspaces/<workspaceId>/signals/<signalId> \
  -H 'Content-Type: application/json' \
  -d '{ "payload": { "foo": "bar" } }'
```

The request body accepts two fields:

| Field      | Type   | Description                                            |
| ---------- | ------ | ------------------------------------------------------ |
| `payload`  | object | Signal payload — validated against `schema` if present |
| `streamId` | string | Optional stream ID for Server-Sent Events progress     |

The response is an execution handle that streaming clients can attach to.

<Tip>
  `friday signal trigger -n <signalId> -w <workspaceId>` is the CLI wrapper for this call. See the [CLI reference](/reference/cli) for the full command shape.
</Tip>

## Payload validation

If a signal declares a `schema` block, the payload is validated before the job runs. Invalid requests get a 400 with the specific validation error; the job never starts.

```yaml workspace.yml theme={null}
signals:
  deploy:
    title: "Deploy"
    description: "Trigger a deployment"
    provider: http
    config:
      path: "/deploy"
    schema:
      type: object
      required: [environment, version]
      properties:
        environment:
          type: string
          enum: [staging, production]
        version:
          type: string
          pattern: "^v[0-9]+\\.[0-9]+\\.[0-9]+$"
```

## Signature verification

For external webhooks, the tunnel verifies HMAC-SHA256 signatures using the `signature_header` defined in the mapping. Set the shared secret on the external service's side; Friday looks it up from space credentials.

If signature verification fails, the tunnel returns 401 and the event never reaches Friday.

## Troubleshooting

<AccordionGroup>
  <Accordion title="Tunnel URL keeps changing">
    The default development tunnel is ephemeral — it gets a new random URL on every restart. For production, set `TUNNEL_TOKEN` to use a named Cloudflare tunnel with a stable hostname, or point the external service at a real reverse proxy.
  </Accordion>

  <Accordion title="Events are delivered but the job doesn't run">
    Check that the event type is in the `events:` list for your provider in `webhook-mappings.yml`. Events not listed are dropped silently. Also check that `actions` (if present) includes the action on the incoming event — e.g. GitHub PR events with `action: closed` are filtered out by the default mapping.
  </Accordion>

  <Accordion title="Payload is empty in the job">
    Double-check the dot-paths in the mapping — a typo (e.g. `pull_request.url` instead of `pull_request.html_url`) silently resolves to `undefined` and the job sees an empty payload. Mapping errors don't fail the webhook; they just produce empty fields.
  </Accordion>

  <Accordion title="Signature verification fails">
    The shared secret on the external service must match what Friday has stored. For GitHub, set the webhook secret in repo settings and rotate the corresponding Friday credential in the same push. Trailing whitespace in copied secrets is a common cause.
  </Accordion>
</AccordionGroup>
