# IRLEvents Webhook Integration Guide

This guide will help you integrate IRLEvents webhooks into your application for real-time event updates.

> **New to the IRLEvents API?** Start with the [Agent Guide](./AGENT_GUIDE.md)
> ([live](https://irlevents.io/api/guides/agent-guide)) for the auth and
> endpoint overview, then come back here for webhook specifics.

> **Auth header:** all examples below use the standard
> `Authorization: Bearer api_<64hex>` header. Mint the key in your
> dashboard at https://irlevents.io/profile (API Keys tab) — pick scopes
> appropriate for what your webhook integration needs (typically
> `events:read` so you can read event details referenced in payloads).
> The legacy `X-API-Key` header is still accepted for back-compat with
> older integrations.

## Benefits of Webhooks

Instead of polling our API every few minutes:
- **Save server resources**: Webhooks only send updates when something actually changes
- **Real-time updates**: Get instant notifications when events are created, updated, or RSVPs happen
- **Security**: Cryptographically verify that webhooks are genuinely from IRLEvents
- **Better UX**: Show live updates to your users without delay

---

## Step 1: Get API Credentials

Self-service — sign in at https://irlevents.io and open
**Profile → API Keys**. Click **Create key**, name it (e.g.
*Webhook Integration*), and pick scopes (typically `events:read` is
enough for webhook receivers — you only need to read event details when
processing payloads). Copy the `api_<64hex>` value: it's only shown once.

You'll use this same key both to register webhooks (below) and to read
any event/profile data referenced in delivered payloads.

---

## Step 2: Register Your Webhook Endpoint

### 2.1 Create Your Webhook Endpoint

First, create an HTTPS endpoint in your application to receive webhooks:

```typescript
// Example webhook endpoint (Express.js)
import express from 'express';
import crypto from 'crypto';

const app = express();

app.post('/webhooks/irlevents', express.json(), async (req, res) => {
  try {
    const signature = req.headers['x-irlevents-signature'];
    const payload = req.body;

    // Verify signature (see Step 3)
    const isValid = verifyWebhookSignature(
      payload,
      signature,
      process.env.IRLEVENTS_WEBHOOK_SECRET
    );

    if (!isValid) {
      console.error('Invalid webhook signature!');
      return res.status(401).send('Invalid signature');
    }

    // Process the webhook event
    await handleWebhookEvent(payload);

    res.json({ received: true });
  } catch (error) {
    console.error('Webhook error:', error);
    res.status(500).json({ error: 'Internal error' });
  }
});

async function handleWebhookEvent(payload) {
  const { type, data, timestamp } = payload;

  console.log(`[Webhook] Received ${type} at ${timestamp}`);

  switch (type) {
    case 'event.created':
      // Handle new event
      await onEventCreated(data);
      break;

    case 'event.updated':
      // Handle event update
      await onEventUpdated(data);
      break;

    case 'rsvp.created':
      // Handle new RSVP
      await onRsvpCreated(data);
      break;

    case 'checkin.completed':
      // Handle check-in
      await onCheckinCompleted(data);
      break;

    default:
      console.log(`Unhandled webhook type: ${type}`);
  }
}
```

**Important Requirements:**
- Endpoint must use **HTTPS** (not HTTP)
- Must be publicly accessible
- Should respond within 10 seconds
- Should return 2xx status code for successful processing

### 2.2 Register the Webhook with IRLEvents

Use the webhook registration API to register your endpoint:

```bash
curl -X POST "https://irlevents.io/api/webhooks/register" \
  -H "Authorization: Bearer api_..." \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://yourapp.com/webhooks/irlevents",
    "events": [
      "event.created",
      "event.updated",
      "event.deleted",
      "rsvp.created",
      "rsvp.canceled",
      "checkin.completed"
    ]
  }'
```

**Response:**
```json
{
  "id": "webhook_abc123xyz",
  "url": "https://yourapp.com/webhooks/irlevents",
  "events": [
    "event.created",
    "event.updated",
    "event.deleted",
    "rsvp.created",
    "rsvp.canceled",
    "checkin.completed"
  ],
  "secret": "whsec_5f8a9b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0",
  "active": true,
  "createdAt": "2025-12-12T10:30:00Z",
  "message": "Webhook registered successfully. Save the secret to verify webhook signatures."
}
```

**CRITICAL**: Save the `secret` value securely! You'll need it to verify webhook signatures.

---

## Step 3: Store the Webhook Secret

### For Supabase Projects:

```bash
npx supabase secrets set IRLEVENTS_WEBHOOK_SECRET=whsec_5f8a9b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0
```

### For Other Environments:

Store in your `.env` file (never commit to git):
```
IRLEVENTS_WEBHOOK_SECRET=whsec_5f8a9b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0
```

Or use your cloud provider's secrets manager (AWS Secrets Manager, GCP Secret Manager, Azure Key Vault, etc.)

---

## Step 4: Verify Webhook Signatures

**ALWAYS verify webhook signatures** to ensure the request is genuinely from IRLEvents and hasn't been tampered with.

### Signature Verification Function

```typescript
import crypto from 'crypto';

function verifyWebhookSignature(
  payload: any,
  signature: string,
  secret: string
): boolean {
  const payloadString = JSON.stringify(payload);
  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(payloadString)
    .digest('hex');

  // Use timing-safe comparison to prevent timing attacks
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expectedSignature)
  );
}
```

### Webhook Request Headers

Each webhook request includes these headers:
- `X-IRLEvents-Signature`: HMAC-SHA256 signature of the payload
- `X-IRLEvents-Event`: The event type (e.g., "event.created")
- `X-IRLEvents-Webhook-ID`: Your webhook ID
- `Content-Type`: application/json

---

## Step 5: Handle Webhook Events

### Event Types and Payloads

#### `event.created`

Triggered when a new event is created.

```json
{
  "type": "event.created",
  "timestamp": "2025-12-12T10:30:00Z",
  "data": {
    "id": "event_abc123",
    "title": "Web3 Meetup",
    "description": "Join us for networking",
    "date": "2025-12-20",
    "time": "18:00",
    "location": "San Francisco, CA",
    "imageUrl": "/uploads/event-abc123-1024.webp",
    "createdBy": "0x1234...5678",
    "createdAt": "2025-12-12T10:30:00Z",
    "category": "meetup",
    "capacity": 100,
    "gates": {
      "mode": "token",
      "groups": [
        {
          "id": "gate_1",
          "chainId": 1,
          "standard": "erc721",
          "contract": "0xabcd...ef01",
          "label": "NFT Holders"
        }
      ]
    }
  }
}
```

#### `event.updated`

Triggered when an event is updated.

```json
{
  "type": "event.updated",
  "timestamp": "2025-12-12T11:00:00Z",
  "data": {
    "id": "event_abc123",
    "title": "Web3 Meetup - Updated Title",
    "description": "Updated description",
    "date": "2025-12-20",
    "time": "19:00",
    "location": "San Francisco, CA",
    "imageUrl": "/uploads/event-abc123-1024.webp",
    "updatedAt": "2025-12-12T11:00:00Z",
    "category": "meetup",
    "capacity": 150,
    "gates": { ... }
  }
}
```

#### `event.deleted`

Triggered when an event is deleted.

```json
{
  "type": "event.deleted",
  "timestamp": "2025-12-12T12:00:00Z",
  "data": {
    "id": "event_abc123",
    "deletedBy": "0x1234...5678",
    "deletedAt": "2025-12-12T12:00:00Z"
  }
}
```

#### `rsvp.created`

Triggered when someone RSVPs to an event.

```json
{
  "type": "rsvp.created",
  "timestamp": "2025-12-12T13:00:00Z",
  "data": {
    "rsvpId": "rsvp_xyz789",
    "eventId": "event_abc123",
    "userId": "0x5678...9abc",
    "guests": 2,
    "status": "confirmed",
    "createdAt": "2025-12-12T13:00:00Z",
    "event": {
      "id": "event_abc123",
      "title": "Web3 Meetup",
      "date": "2025-12-20",
      "time": "18:00"
    }
  }
}
```

#### `rsvp.canceled`

Triggered when an RSVP is canceled.

```json
{
  "type": "rsvp.canceled",
  "timestamp": "2025-12-12T14:00:00Z",
  "data": {
    "rsvpId": "rsvp_xyz789",
    "eventId": "event_abc123",
    "userId": "0x5678...9abc",
    "canceledAt": "2025-12-12T14:00:00Z"
  }
}
```

#### `checkin.completed`

Triggered when someone checks in to an event.

```json
{
  "type": "checkin.completed",
  "timestamp": "2025-12-20T18:15:00Z",
  "data": {
    "rsvpId": "rsvp_xyz789",
    "eventId": "event_abc123",
    "userId": "0x5678...9abc",
    "checkedInAt": "2025-12-20T18:15:00Z",
    "checkedInBy": "0x1234...5678",
    "event": {
      "id": "event_abc123",
      "title": "Web3 Meetup",
      "date": "2025-12-20"
    }
  }
}
```

---

## Step 6: Manage Webhooks

### List All Webhooks

```bash
curl -X GET "https://irlevents.io/api/webhooks" \
  -H "Authorization: Bearer api_..."
```

### Get Specific Webhook (Including Secret)

```bash
curl -X GET "https://irlevents.io/api/webhooks/webhook_abc123xyz" \
  -H "Authorization: Bearer api_..."
```

### Update Webhook

```bash
curl -X PATCH "https://irlevents.io/api/webhooks/webhook_abc123xyz" \
  -H "Authorization: Bearer api_..." \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://yourapp.com/new-webhook-endpoint",
    "events": ["event.created", "rsvp.created"],
    "active": true
  }'
```

### Delete Webhook

```bash
curl -X DELETE "https://irlevents.io/api/webhooks/webhook_abc123xyz" \
  -H "Authorization: Bearer api_..."
```

### Test Webhook

Send a test webhook to verify your endpoint is working:

```bash
curl -X POST "https://irlevents.io/api/webhooks/webhook_abc123xyz/test" \
  -H "Authorization: Bearer api_..."
```

---

## Best Practices

### 1. Idempotency

Process webhooks idempotently - the same webhook might be delivered multiple times:

```typescript
async function handleWebhookEvent(payload) {
  const { type, data } = payload;

  // Check if we've already processed this webhook
  const existing = await db.webhookLog.findUnique({
    where: {
      webhookId: payload.data.id,
      timestamp: payload.timestamp
    }
  });

  if (existing) {
    console.log('Webhook already processed, skipping');
    return;
  }

  // Process webhook...

  // Log that we've processed it
  await db.webhookLog.create({
    data: {
      webhookId: payload.data.id,
      timestamp: payload.timestamp,
      type: payload.type
    }
  });
}
```

### 2. Error Handling

Return 2xx for successful processing, even if there are non-critical errors:

```typescript
app.post('/webhooks/irlevents', async (req, res) => {
  try {
    // Verify signature first
    if (!verifySignature(...)) {
      return res.status(401).send('Invalid signature');
    }

    // Respond immediately
    res.json({ received: true });

    // Process asynchronously (don't await)
    processWebhook(req.body).catch(err => {
      console.error('Webhook processing error:', err);
      // Log to error tracking service
    });
  } catch (error) {
    console.error('Webhook error:', error);
    res.status(500).json({ error: 'Internal error' });
  }
});
```

### 3. Retry Handling

IRLEvents will retry failed webhooks with exponential backoff:
- Retry 1: After 1 minute
- Retry 2: After 5 minutes
- Retry 3: After 30 minutes
- Retry 4-10: Every hour

After 10 consecutive failures, the webhook will be automatically disabled.

### 4. Security Checklist

- ✅ Always use HTTPS endpoints
- ✅ Always verify webhook signatures
- ✅ Store secrets in environment variables/secret managers
- ✅ Never commit secrets to version control
- ✅ Use timing-safe comparison for signature verification
- ✅ Rate limit webhook endpoints to prevent abuse
- ✅ Log webhook deliveries for debugging

---

## Troubleshooting

### Webhook Not Being Delivered

1. **Check webhook status**: Ensure `active: true`
2. **Verify URL**: Must be HTTPS and publicly accessible
3. **Check failure count**: Auto-disabled after 10 failures
4. **Test endpoint**: Use the `/test` endpoint to verify

### Signature Verification Failing

1. **Check secret**: Ensure you're using the correct secret
2. **Verify payload**: Must stringify JSON exactly as received
3. **Check headers**: Signature is in `X-IRLEvents-Signature` header
4. **Timing issues**: Process webhook immediately, don't buffer

### High Latency

1. **Respond quickly**: Acknowledge receipt before processing
2. **Process async**: Use background jobs for heavy processing
3. **Optimize queries**: Index database fields used in webhook handlers

---

## Example Implementation (Complete)

Here's a complete webhook receiver implementation:

```typescript
import express from 'express';
import crypto from 'crypto';

const app = express();

// Webhook signature verification
function verifyWebhookSignature(
  payload: any,
  signature: string,
  secret: string
): boolean {
  const payloadString = JSON.stringify(payload);
  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(payloadString)
    .digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expectedSignature)
  );
}

// Webhook endpoint
app.post('/webhooks/irlevents', express.json(), async (req, res) => {
  try {
    const signature = req.headers['x-irlevents-signature'] as string;
    const payload = req.body;

    // Verify signature
    const isValid = verifyWebhookSignature(
      payload,
      signature,
      process.env.IRLEVENTS_WEBHOOK_SECRET!
    );

    if (!isValid) {
      console.error('❌ Invalid webhook signature');
      return res.status(401).send('Invalid signature');
    }

    // Respond immediately
    res.json({ received: true });

    // Process asynchronously
    processWebhook(payload).catch(err => {
      console.error('Webhook processing error:', err);
    });
  } catch (error) {
    console.error('Webhook endpoint error:', error);
    res.status(500).json({ error: 'Internal error' });
  }
});

// Process webhook events
async function processWebhook(payload: any) {
  const { type, data, timestamp } = payload;

  console.log(`[Webhook] ${type} at ${timestamp}`);

  switch (type) {
    case 'event.created':
      await handleEventCreated(data);
      break;

    case 'event.updated':
      await handleEventUpdated(data);
      break;

    case 'event.deleted':
      await handleEventDeleted(data);
      break;

    case 'rsvp.created':
      await handleRsvpCreated(data);
      break;

    case 'rsvp.canceled':
      await handleRsvpCanceled(data);
      break;

    case 'checkin.completed':
      await handleCheckinCompleted(data);
      break;

    default:
      console.log(`Unhandled webhook type: ${type}`);
  }
}

// Event handlers
async function handleEventCreated(data: any) {
  console.log(`New event: ${data.title} (${data.id})`);

  // Update your database
  await db.events.upsert({
    where: { id: data.id },
    create: {
      id: data.id,
      title: data.title,
      description: data.description,
      date: data.date,
      capacity: data.capacity,
      // ... other fields
    },
    update: {
      title: data.title,
      // ... other fields
    }
  });

  // Invalidate caches
  await cache.delete(`event:${data.id}`);
}

async function handleRsvpCreated(data: any) {
  console.log(`New RSVP: ${data.userId} -> ${data.event.title}`);

  // Update available seats
  await db.events.update({
    where: { id: data.eventId },
    data: {
      availableSeats: { decrement: data.guests }
    }
  });

  // Send user notification
  await sendNotification(data.userId, {
    title: 'RSVP Confirmed',
    message: `You're confirmed for ${data.event.title}`,
    link: `/events/${data.eventId}`
  });
}

// Start server
app.listen(3000, () => {
  console.log('Webhook server listening on port 3000');
});
```

---

## Support

If you have questions or issues:
- Email: support@irlevents.io
- Documentation: https://irlevents.io/docs/webhooks
- Discord: https://discord.gg/irlevents

---

## Summary

1. ✅ Request API credentials from IRLEvents team
2. ✅ Create HTTPS webhook endpoint in your app
3. ✅ Register webhook via `/api/webhooks/register`
4. ✅ Store the webhook secret securely
5. ✅ Verify signatures on all webhook requests
6. ✅ Handle webhook events and update your database
7. ✅ Monitor webhook delivery and handle failures

With webhooks configured, you'll receive real-time updates instead of polling the API, saving server resources and providing a better user experience!
