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

# Create Webhook

> Create a webhook for real-time notifications

## Description

Create a webhook to receive real-time notifications of Sendly events.

## Parameters

<ParamField body="url" type="string" required>
  HTTPS URL where to receive notifications. Example: `https://yourdomain.com/webhooks/sendly`
</ParamField>

<ParamField body="events" type="string[]" required>
  Array of events to receive.

  Available events:

  * `email.sent`: Email was sent
  * `email.delivered`: Email was delivered
  * `email.bounced`: Email bounced
  * `email.failed`: Send failed
  * `email.opened`: Email was opened
  * `email.clicked`: Link was clicked
</ParamField>

## Response

<ResponseField name="webhook" type="object">
  Created webhook object with `id`, `url`, `active`, `created_at`
</ResponseField>

<ResponseField name="signing_key" type="string">
  Secret key for validating webhooks. **Save this securely!**
</ResponseField>

## Examples

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://api.usesendly.app/v1/webhooks \
    -H "Authorization: Bearer se_your_api_key" \
    -H "Content-Type: application/json" \
    -d '{
      "url": "https://yourdomain.com/webhooks/sendly",
      "events": [
        "email.delivered",
        "email.bounced",
        "email.opened",
        "email.clicked"
      ]
    }'
  ```

  ```javascript SDK theme={null}
  const webhook = await client.webhooks.create({
    url: 'https://yourdomain.com/webhooks/sendly',
    events: [
      'email.delivered',
      'email.bounced',
      'email.opened',
      'email.clicked'
    ]
  })

  console.log('Webhook ID:', webhook.webhook.id)
  console.log('Signing key:', webhook.signing_key)
  ```

  ```javascript SMTP theme={null}
  import { Sendly } from '@sendlyapp/sdk'

  const client = new Sendly()
  const webhook = await client.webhooks.create({
    url: 'https://yourdomain.com/webhooks/sendly',
    events: ['email.delivered', 'email.bounced']
  })
  ```
</CodeGroup>

## Validate Webhook Signature

<CodeGroup>
  ```javascript SDK theme={null}
  import crypto from 'crypto'
  import express from 'express'

  const app = express()
  const WEBHOOK_SECRET = 'your_signing_key'

  function validateWebhookSignature(payload, signature) {
    const hash = crypto
      .createHmac('sha256', WEBHOOK_SECRET)
      .update(JSON.stringify(payload))
      .digest('hex')
    
    return hash === signature
  }

  app.post('/webhooks/sendly', (req, res) => {
    const signature = req.headers['x-sendly-signature']
    
    if (!validateWebhookSignature(req.body, signature)) {
      return res.status(401).send('Invalid signature')
    }
    
    // Process webhook event
    handleEvent(req.body)
    
    res.send('OK')
  })

  function handleEvent(event) {
    console.log(`Event: ${event.type}`)
    
    switch (event.type) {
      case 'email.delivered':
        console.log(`Email delivered: ${event.data.email_id}`)
        break
      case 'email.opened':
        console.log(`Email opened: ${event.data.email_id}`)
        break
    }
  }
  ```

  ```python SMTP theme={null}
  from flask import Flask, request
  import hmac
  import hashlib
  import json

  app = Flask(__name__)
  WEBHOOK_SECRET = b'your_signing_key'

  def validate_webhook(payload, signature):
      hash_obj = hmac.new(WEBHOOK_SECRET, payload, hashlib.sha256)
      return hash_obj.hexdigest() == signature

  @app.route('/webhooks/sendly', methods=['POST'])
  def webhook():
      signature = request.headers.get('X-Sendly-Signature')
      payload = request.get_data()
      
      if not validate_webhook(payload, signature):
          return 'Invalid signature', 401
      
      event = json.loads(payload)
      print(f"Event: {event['type']}")
      
      return 'OK', 200
  ```
</CodeGroup>

## Webhook Events

### email.delivered

```json theme={null}
{
  "type": "email.delivered",
  "data": {
    "email_id": "email_123",
    "to": "user@example.com",
    "subject": "Subject",
    "delivered_at": "2024-01-15T10:30:00Z"
  }
}
```

### email.opened

```json theme={null}
{
  "type": "email.opened",
  "data": {
    "email_id": "email_123",
    "to": "user@example.com",
    "opened_at": "2024-01-15T11:00:00Z",
    "user_agent": "Mozilla/5.0..."
  }
}
```

### email.clicked

```json theme={null}
{
  "type": "email.clicked",
  "data": {
    "email_id": "email_123",
    "to": "user@example.com",
    "url": "https://example.com",
    "clicked_at": "2024-01-15T11:05:00Z"
  }
}
```

## Retries

If your webhook returns non-2xx status, Sendly will retry:

* 1st: 5 minutes later
* 2nd: 30 minutes later
* 3rd: 2 hours later
* 4th: 24 hours later

## Best Practices

✅ **Always validate** the `x-sendly-signature` header
✅ **Respond quickly** (\< 30 seconds)
✅ **Use HTTPS** required
✅ **Store key securely** in environment variables
✅ **Log events** for audit trail
✅ **Handle idempotence** - process each event once

***

Need help? Contact [support@usesendly.app](mailto:support@usesendly.app)
