---
title: "Events"
description: "How code forges deliver changes, and how providers turn them into one event type."
canonical_url: "https://docs-oquac2tli-poetry.vercel.app/concepts/events"
---
# Events

> How code forges deliver changes, and how providers turn them into one event type.

A `ForgeEvent` describes something that happened, such as a comment, a push, a review or a label change. Providers produce events from three sources:

<table>
<thead>
  <tr>
    <th>
      Source
    </th>
    
    <th>
      How it works
    </th>
    
    <th>
      Capability
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        webhook
      </code>
    </td>
    
    <td>
      The forge sends an HTTP request to your server.
    </td>
    
    <td>
      <code>
        sources.webhook
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        poll
      </code>
    </td>
    
    <td>
      You list notifications or thread events on a schedule.
    </td>
    
    <td>
      <code>
        sources.poll
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        subscribe
      </code>
    </td>
    
    <td>
      You keep a connection open and receive events as they happen.
    </td>
    
    <td>
      <code>
        sources.subscribe
      </code>
    </td>
  </tr>
</tbody>
</table>

`event.source` tells you which one delivered an event. Most forges support webhooks. Tangled also offers `subscribe` through Jetstream. GitHub, GitLab, Forgejo, Gitea and Gitee can be polled.

## Shape of an event

<table>
<thead>
  <tr>
    <th>
      Field
    </th>
    
    <th>
      Content
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        kind
      </code>
    </td>
    
    <td>
      What kind of thing happened, such as <code>
        comment
      </code>
      
      , <code>
        review
      </code>
      
      , <code>
        state_change
      </code>
      
      , <code>
        label
      </code>
      
      , <code>
        push
      </code>
      
       or <code>
        release
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        action
      </code>
    </td>
    
    <td>
      The verb, such as <code>
        opened
      </code>
      
      , <code>
        closed
      </code>
      
      , <code>
        merged
      </code>
      
      , <code>
        created
      </code>
      
       or <code>
        labelled
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        summary
      </code>
    </td>
    
    <td>
      One line that suits a list row
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        actor
      </code>
      
      , <code>
        repo
      </code>
      
      , <code>
        thread
      </code>
    </td>
    
    <td>
      Who did it, and where, when the event has them
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        detail
      </code>
    </td>
    
    <td>
      Typed fields for the kind, discriminated by <code>
        detail.type
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        occurredAt
      </code>
    </td>
    
    <td>
      A <code>
        Date
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        payload
      </code>
    </td>
    
    <td>
      The unnormalised delivery
    </td>
  </tr>
</tbody>
</table>

## Raw names stay available

The normalised `kind` and `action` come from a forge's own event name and verb, which stay in `kindRaw` and `actionRaw`. When a forge uses wording that has no equivalent, `action` is `other` and the raw name still tells you what happened.

On GitLab, event kinds for some thread changes come from the text of system notes rather than a field. That provider reports `eventKinds: 'heuristic'` in its capabilities. For an unrecognised note, `kind` can be `other`, and `kindRaw` and `payload` stay exact.

## Subscriptions

`sources.subscribe()` returns an async iterable of `{ event, cursor }`. Save the cursor and pass it back to resume. The provider doesn't reconnect when the connection drops. It throws `SubscriptionClosedError` with the last cursor, and you call `subscribe()` again:

```ts
for await (const { event, cursor } of reader.sources.subscribe({
  cursor: saved,
  signal,
})) {
  console.log(event.kind, event.summary)
  saved = cursor
}
```

## Next steps

- [Receive webhooks](https://docs-oquac2tli-poetry.vercel.app/guides/webhooks)
- [Page through results](https://docs-oquac2tli-poetry.vercel.app/guides/pagination) for polling with cursors


## Sitemap

See the full [sitemap](https://docs-oquac2tli-poetry.vercel.app/sitemap.md) for all pages.
