Appearance
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
- Go to app.extensionlogin.com
- Navigate to CRM Connections
- Click "Add Connection"
- Select "Webhook"
- Configure your webhook:
| Field | Description |
|---|---|
| Name | Friendly name for this webhook |
| URL | Your endpoint URL (must be HTTPS) |
| Method | POST (default), PUT, or PATCH |
| Headers | Custom 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
| Event | Description |
|---|---|
user.identified | New 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
- Go to your webhook connection in dashboard
- Click "Show Secret"
- Copy the secret to your server's environment variables
Handling Webhooks
Best Practices
- Return 200 quickly: Respond within 5 seconds
- Process async: Queue work for background processing
- Be idempotent: Handle duplicate deliveries gracefully
- 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 configurationDashboard Test Tool
- Go to CRM Connections
- Click on your webhook
- Click "Send Test"
- 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
- Create a Zapier webhook (catch hook)
- Copy the Zapier webhook URL
- Add it as a webhook in ExtensionLogin
- Use Zapier to connect to any app
Make (Integromat)
- Create a webhook module in Make
- Copy the webhook URL
- Add it as a webhook in ExtensionLogin
- 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
- Check the URL is HTTPS and accessible
- Verify firewall allows incoming requests
- Check dashboard logs for delivery attempts
Invalid Signature Errors
- Ensure you're using the correct webhook secret
- Verify you're reading the raw request body
- Check timestamp header is being read correctly
Timeout Errors
- Respond to webhook within 5 seconds
- Process heavy work asynchronously
- Increase server timeout if needed
Next Steps
- CRM Integration - Use built-in CRM connectors
- Custom Fields - Send additional data
- Security - Secure your webhook endpoint