# Add webhooks to SDKs

## Why use the webhooks feature?

- **Built-in SDK support:** It simplifies webhook adoption through built-in SDK support.
- **Abstracted complexity:** Consumers don't need to worry about cryptographic operations or dependencies.
- **Default security:** Consumers must verify the signature to unpack the webhook data.
- **Type-safe consumption:** Consumers get schema-validated data and types.
- **Type-safe sending:** Producers can send schema-validated data through type-safe SDK methods.

## Getting started

<Callout title="Info" type="info">
Webhooks are a paid add-on, [reach out to us to discuss pricing](https://www.speakeasy.com/book-demo).
</Callout>

1. You must have a [Speakeasy Business or Enterprise plan](/pricing).
2. Enable the webhooks add-on to your account under `settings/billing`:

## Example

<Callout title="GitHub example source code" type="info">
You can see the full source code for this example in the [webhooks example repo](https://github.com/speakeasy-api/examples/tree/main/webhooks-ts).
</Callout>

### OpenAPI

```yaml
openapi: 3.1.0 # You must use OpenAPI 3.1.0 or higher
info:
  title: Petstore API
  version: 1.0.0
servers:
  - url: https://petstore.swagger.io/v2
paths:
  /pets:
    post:
      operationId: addPet
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/Pet"
      responses:
        "200":
          description: Okay
x-speakeasy-webhooks:
  security:
    type: signature
    headerName: x-signature
    signatureTextEncoding: base64
    algorithm: hmac-sha256
webhooks:
  pet.created:
    post:
      operationId: sendPetCreated
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PetCreated"
      responses:
        "200":
          description: Okay
  pet.deleted:
    post:
      operationId: sendPetDeleted
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PetDeleted"
      responses:
        "200":
          description: Okay
components:
  schemas:
    PetCreated:
      type: object
      properties:
        type:
          type: string
          enum:
            - pet.created # This is the payload discriminator
        pet:
          $ref: "#/components/schemas/Pet"
      required:
        - type
        - pet
    PetDeleted:
      type: object
      properties:
        type:
          type: string
          enum:
            - pet.deleted # This is the payload discriminator
        id:
          type: string
      required:
        - type
        - id
    Pet:
      type: object
      properties:
        id:
          type: string
      required:
        - id
```

### For webhook consumers

<Callout title="">
The `validateWebhook()` function is currently only implemented in the TypeScript SDK, with support for additional languages planned for future releases. While other languages will generate webhook types, this discriminator method is TypeScript-only.
</Callout>

```typescript

const sdk = new Petstore();

const data = await sdk.validateWebhook({
  request,
  secret: "<secret>",
});

console.log(data);

if (data.type === "pet.created") {
  console.log("Pet created", data.pet);
}

if (data.type === "pet.deleted") {
  console.log("Pet deleted", data.id);
}
```

**Error handling**

```typescript

const sdk = new Petstore();

try {
  await sdk.validateWebhook({
    request,
    secret: "<secret>",
  });
} catch (error) {
  if (error instanceof WebhookAuthenticationError) {
    // Thrown when signature verification fails, usually due to:
    // - Incorrect webhook secret
    // - Modified request payload
    // - Wrong signature format
    console.error("Webhook authentication failed", error);
  }
  if (error instanceof SDKValidationError) {
    // Thrown when the webhook request body is unrecognised, usually due
    // to an outdated SDK version or un-docummented payloads
    console.error("Webhook request body is invalid", error);
  }
  throw error;
}
```

### For webhook producers

```typescript

const sdk = new Petstore();

const data = await sdk.sendPetCreated(
  {
    url: "https://example.com/my-webhook-handler",
    secret: "<secret>",
  },
  {
    type: "pet.created",
    pet: { id: "dog" },
  },
);
```

## `x-speakeasy-webhooks`

The `x-speakeasy-webhooks` extension is used to define the webhooks for your API.

### `x-speakeasy-webhooks.security`

### `x-speakeasy-webhooks.security.type: signature`

```yaml
x-speakeasy-webhooks:
  security:
    type: signature
    headerName: x-signature
    signatureTextEncoding: base64
    algorithm: hmac-sha256
```

This will apply HMAC SHA256 to the body of the webhook request and put it in a header.

### `x-speakeasy-webhooks.security.type: custom`

```yaml
x-speakeasy-webhooks:
  security:
    type: custom
```

This generates the `src/hooks/webhooks-custom-security.ts` boilerplate file, which you can then use to modify the security logic.

See the source code for this example in the [GitHub repo](https://github.com/speakeasy-api/examples/tree/main/webhooks-custom-security-ts).
