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": "user@example.com",
    "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": "test@example.com",
      "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