# Webhooks on the sheet

> The sheet's only way out to the internet: declaring webhooks in the manifest and sending data to Zapier, Make or IFTTT.

Page: https://docs.rpgdesk.com/en/fichas/webhooks/

The sheet has no internet access. Its only way out is **webhooks**: the sheet sends data under a name (“rolls”), and the room’s game master decides where it goes, by pasting the address of a **Zapier**, **Make** or **IFTTT** webhook. That way the sheet never chooses the destination, and the address never shows up for players.

## Declare

In the manifest:

```json
"webhooks": [
  { "name": "rolls", "description": "Every roll from the sheet", "example": { "total": 17, "roll": "1d20+5" } }
]
```

- `name`: lowercase letters, numbers, `-` and `_`. The sheet uses this name to send.
- `description`: what the webhook sends, so the game master knows what they are connecting.
- `example` (optional): a sample of the data, up to 4 KB.
- Up to 20 webhooks per plugin.

When installing, RPG Desk shows the webhooks to the game master, who connects the addresses in **Room settings → Plugins → Integrations**. See [Integrations](https://docs.rpgdesk.com/en/mestres/integracoes/).

## Send

```js
const r = await RPGDesk.webhooks.send('rolls', { total: 17, roll: '1d20+5' });
if (!r.ok) RPGDesk.ui.toast(`The webhook did not go through: ${r.error}`, 'error');
```

The result is `{ ok, status, body, error }`:

| `error`            | When                                                              |
| ------------------ | ----------------------------------------------------------------- |
| `null`             | it worked; `body` is the service’s answer                         |
| `'not-found'`      | the manifest does not declare a webhook with that name            |
| `'not-configured'` | the game master has not connected an address to that webhook yet  |
| `'forbidden'`      | the webhook is **GMs only**, and whoever triggered it is a player |
| `'bad-request'`    | the data is over 16 KB                                            |
| `'rate-limited'`   | too many sends in a short time in the room                        |
| `'timeout'`        | the service did not answer within 10 seconds                      |
| `'failed'`         | the service answered with an error, or could not be reached       |

RPG Desk sends your data in `data`, along with the plugin, the webhook, the room, who triggered it and the time. The format is in [Integrations](https://docs.rpgdesk.com/en/mestres/integracoes/#what-reaches-the-service).

## Example: every roll on Discord

```html
<button id="attack">Attack</button>
<script>
  document.getElementById('attack').addEventListener('click', async () => {
    const r = await RPGDesk.dice.roll('1d20+5', { label: 'Attack' });
    await RPGDesk.webhooks.send('rolls', { character: RPGDesk.doc.get('name'), total: r.total });
  });
</script>
```

On the other side, a Zap with the **Catch Hook** trigger and an action that sends a message to a Discord channel, using `data.character` and `data.total`.

> **Limits**
>
> Up to 20 sends per minute on each open sheet (beyond that, `send` fails with an error) and 30 per minute in the whole room. The data goes as JSON, up to 16 KB; the service’s answer comes back up to 64 KB. In test mode, nothing goes out: the **Log** shows what would be sent.
