Skip to content

Webhooks

Send user data to any HTTP endpoint using webhooks.

Overview

Webhooks allow you to receive user data at your own server whenever someone signs up through your extension. This is useful for:

  • Custom CRM integrations
  • Internal databases
  • Analytics platforms
  • Automation workflows (Zapier, Make, etc.)

Setting Up a Webhook

In the Dashboard

  1. Go to app.extensionlogin.com
  2. Navigate to CRM Connections
  3. Click "Add Connection"
  4. Select "Webhook"
  5. Configure your webhook:
FieldDescription
NameFriendly name for this webhook
URLYour endpoint URL (must be HTTPS)
MethodPOST (default), PUT, or PATCH
HeadersCustom headers (JSON format)

Webhook Payload

When a user is identified, your webhook receives:

json
{
  "event": "user.identified",
  "timestamp": "2024-01-15T10:30:00.000Z",
  "user": {
    "id": "usr_a1b2c3d4e5",
    "email": "[email protected]",
    "name": "John Doe",
    "firstName": "John",
    "lastName": "Doe",
    "phone": "+1-555-123-4567",
    "metadata": {
      "plan": "premium",
      "source": "chrome_extension"
    },
    "createdAt": "2024-01-15T10:30:00.000Z",
    "updatedAt": "2024-01-15T10:30:00.000Z"
  },
  "application": {
    "id": "app_xxxxx",
    "name": "Your Extension Name"
  }
}

Event Types

EventDescription
user.identifiedNew user identified or existing user updated

Headers

ExtensionLogin sends these headers with every webhook:

Content-Type: application/json
X-ExtensionLogin-Event: user.identified
X-ExtensionLogin-Timestamp: 1705315800000
X-ExtensionLogin-Signature: sha256=abc123...

Custom Headers

Add your own headers in JSON format:

json
{
  "Authorization": "Bearer your-api-key",
  "X-Custom-Header": "custom-value"
}

Webhook Security

Signature Verification

Every webhook includes a signature to verify it came from ExtensionLogin.

Node.js Example:

javascript
const crypto = require('crypto');

function verifyWebhook(req, secret) {
  const signature = req.headers['x-extensionlogin-signature'];
  const timestamp = req.headers['x-extensionlogin-timestamp'];

  // Prevent replay attacks - reject old webhooks
  const age = Date.now() - parseInt(timestamp);
  if (age > 300000) { // 5 minutes
    return false;
  }

  // Compute expected signature
  const payload = JSON.stringify(req.body);
  const signedPayload = `${timestamp}.${payload}`;
  const expectedSignature = 'sha256=' + crypto
    .createHmac('sha256', secret)
    .update(signedPayload)
    .digest('hex');

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

// Express.js middleware
app.post('/webhook', express.json(), (req, res) => {
  const secret = process.env.WEBHOOK_SECRET;

  if (!verifyWebhook(req, secret)) {
    return res.status(401).json({ error: 'Invalid signature' });
  }

  // Process the webhook
  const { event, user } = req.body;
  console.log(`${event}: ${user.email}`);

  res.status(200).json({ received: true });
});

Python Example:

python
import hmac
import hashlib
import time

def verify_webhook(request, secret):
    signature = request.headers.get('X-ExtensionLogin-Signature')
    timestamp = request.headers.get('X-ExtensionLogin-Timestamp')

    # Prevent replay attacks
    age = time.time() * 1000 - int(timestamp)
    if age > 300000:  # 5 minutes
        return False

    # Compute expected signature
    payload = request.get_data(as_text=True)
    signed_payload = f"{timestamp}.{payload}"
    expected = 'sha256=' + hmac.new(
        secret.encode(),
        signed_payload.encode(),
        hashlib.sha256
    ).hexdigest()

    return hmac.compare_digest(signature, expected)

Getting Your Webhook Secret

  1. Go to your webhook connection in dashboard
  2. Click "Show Secret"
  3. Copy the secret to your server's environment variables

Handling Webhooks

Best Practices

  1. Return 200 quickly: Respond within 5 seconds
  2. Process async: Queue work for background processing
  3. Be idempotent: Handle duplicate deliveries gracefully
  4. Log everything: Keep records for debugging

Example: Express.js Handler

javascript
const express = require('express');
const app = express();

app.post('/extensionlogin-webhook', express.json(), async (req, res) => {
  // Verify signature first
  if (!verifySignature(req)) {
    return res.status(401).send('Unauthorized');
  }

  const { event, user } = req.body;

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

  // Process async
  try {
    switch (event) {
      case 'user.identified':
        await handleNewUser(user);
        break;
      default:
        console.log('Unknown event:', event);
    }
  } catch (error) {
    console.error('Webhook processing error:', error);
    // Don't throw - webhook already acknowledged
  }
});

async function handleNewUser(user) {
  // Add to your database
  await db.users.upsert({
    email: user.email,
    name: user.name,
    extensionLoginId: user.id
  });

  // Send welcome email
  await sendWelcomeEmail(user.email, user.name);
}

Example: Serverless (AWS Lambda)

javascript
exports.handler = async (event) => {
  const body = JSON.parse(event.body);

  // Verify signature
  if (!verifySignature(event.headers, body)) {
    return { statusCode: 401, body: 'Unauthorized' };
  }

  const { user } = body;

  // Process user
  await processUser(user);

  return { statusCode: 200, body: JSON.stringify({ received: true }) };
};

Retry Logic

ExtensionLogin retries failed webhooks:

  • Attempts: 3 retries
  • Backoff: Exponential (1s, 2s, 4s)
  • Timeout: 30 seconds per attempt

A webhook is considered successful when:

  • HTTP status is 2xx
  • Response is received within timeout

Testing Webhooks

Local Development

Use a tunnel service to test locally:

bash
# Using ngrok
ngrok http 3000
# Use the ngrok URL in your webhook configuration

Dashboard Test Tool

  1. Go to CRM Connections
  2. Click on your webhook
  3. Click "Send Test"
  4. Check your server logs

Test Payload

bash
# Manual test with curl
curl -X POST https://your-server.com/webhook \
  -H "Content-Type: application/json" \
  -H "X-ExtensionLogin-Event: user.identified" \
  -d '{
    "event": "user.identified",
    "user": {
      "email": "[email protected]",
      "name": "Test User"
    }
  }'

Integration Examples

Zapier

  1. Create a Zapier webhook (catch hook)
  2. Copy the Zapier webhook URL
  3. Add it as a webhook in ExtensionLogin
  4. Use Zapier to connect to any app

Make (Integromat)

  1. Create a webhook module in Make
  2. Copy the webhook URL
  3. Add it as a webhook in ExtensionLogin
  4. Build your automation flow

Slack Notification

javascript
app.post('/webhook', async (req, res) => {
  const { user } = req.body;

  // Send to Slack
  await fetch(process.env.SLACK_WEBHOOK_URL, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      text: `New user signup: ${user.name} (${user.email})`
    })
  });

  res.status(200).send('OK');
});

Troubleshooting

Webhook Not Receiving Data

  1. Check the URL is HTTPS and accessible
  2. Verify firewall allows incoming requests
  3. Check dashboard logs for delivery attempts

Invalid Signature Errors

  1. Ensure you're using the correct webhook secret
  2. Verify you're reading the raw request body
  3. Check timestamp header is being read correctly

Timeout Errors

  1. Respond to webhook within 5 seconds
  2. Process heavy work asynchronously
  3. Increase server timeout if needed

Next Steps

Built for Chrome Extension Developers