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

# sendEvent

> Posts one of your declared webhook events to Tappify, signed with the install's inbound secret.

```ts theme={null}
function sendEvent<E>(
   name, 
   payload, 
   options
): Promise<void>;
```

Posts one of your declared webhook events to Tappify, signed with the install's
inbound secret.

## Type Parameters

### E

`E` *extends* `string`

## Parameters

### name

`E`

### payload

[`WebhookPayload`](/extensions/reference/server-api/TypeAlias.WebhookPayload)\<`E`>

### options

[`SendEventOptions`](/extensions/reference/server-api/Interface.SendEventOptions)

## Returns

`Promise`\<`void`>

## Remarks

The name and the payload are narrowed by the `TapWebhookMap` augmentation
`tappify extension types` writes. It builds the envelope, signs the serialised
body and posts it to the install's hook path under `baseUrl`, or to `endpoint`
when you give one. A response Tappify did not accept rejects with a
`TapServerError` whose `code` is always `TAP_WEBHOOK_REJECTED`, carrying the HTTP
status, and with Tappify's own code in the message text rather than in `code`;
the two usual causes are the wrong signing
secret and an event the manifest does not declare. Pass your own `dedupeKey` to
make a retry idempotent, because the default is a fresh UUID.

## Example

```ts theme={null}
import { sendEvent } from '@tappify/extension-sdk/server';

async function reportAnomaly(installId: string, why: string): Promise<void> {
  await sendEvent('anomaly.detected', { why }, {
    installId,
    extensionId: 'starter',
    secret: process.env.TAPPIFY_INBOUND_SECRET ?? '',
    dedupeKey: `anomaly-${installId}-${why}`,
  });
}
```
