Webhooks
Receive real-time notifications when events happen in your Brighten organization. 18 event types across 5 categories.
Event Types
Recognitions
| Event | Description |
|---|---|
recognition.created | A new recognition was sent |
recognition.updated | A recognition was edited |
recognition.deleted | A recognition was removed |
Users
| Event | Description |
|---|---|
user.created | A new user was added to the organization |
user.updated | A user profile was updated |
user.deleted | A user was removed from the organization |
user.points.updated | A user's points balance changed |
Rewards
| Event | Description |
|---|---|
reward.created | A new reward was added to the catalog |
reward.updated | A reward was modified |
reward.redeemed | A user redeemed a reward |
redemption.fulfilled | A reward redemption was fulfilled |
redemption.cancelled | A reward redemption was cancelled |
Teams
| Event | Description |
|---|---|
team.created | A new team was created |
team.updated | A team was modified |
team.member.added | A member was added to a team |
team.member.removed | A member was removed from a team |
Budgets
| Event | Description |
|---|---|
budget.low_balance | A budget is running low |
budget.depleted | A budget has been fully spent |
budget.refilled | A budget was refilled |
Payload Structure
Every webhook delivery sends a JSON payload with a consistent envelope structure.
Example Payload
{
"id": "evt_abc123def456",
"object": "event",
"type": "recognition.created",
"created_at": "2026-02-06T14:00:00Z",
"api_version": "2026-01-01",
"data": {
"object": {
"id": "550e8400-e29b-41d4-a716-446655440003",
"sender": { "id": "...", "name": "Jane Doe" },
"recipient": { "id": "...", "name": "John Smith" },
"message": "Great work on the launch!",
"points": 50
}
},
"metadata": {
"organization_id": "org_xyz789",
"triggered_by": "user_abc123"
}
}| Field | Type | Description |
|---|---|---|
| id | string | Unique event ID |
| object | string | Always "event" |
| type | string | The event type (e.g. recognition.created) |
| created_at | string | ISO 8601 timestamp |
| api_version | string | API version this payload conforms to |
| data.object | object | The resource that was affected |
| data.previous | object? | Previous state (update events only) |
| metadata | object | Organization ID and triggering user |
Signature Verification
Every webhook delivery includes an X-Brighten-Signature header containing an HMAC-SHA256 signature. Always verify this to ensure the payload was sent by Brighten.
How verification works:
- Extract the
X-Brighten-Signatureheader - Compute HMAC-SHA256 of the raw request body using your webhook secret
- Compare the computed signature with the header value
- Reject the request if signatures do not match
TypeScript
import crypto from 'crypto';
function verifyWebhookSignature(payload: string, signature: string, secret: string): boolean {
const expected = crypto.createHmac('sha256', secret).update(payload).digest('hex');
return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
}
// In your webhook handler:
app.post('/webhooks/brighten', (req, res) => {
const signature = req.headers['x-brighten-signature'];
const isValid = verifyWebhookSignature(req.rawBody, signature, process.env.BRIGHTEN_WEBHOOK_SECRET);
if (!isValid) return res.status(401).json({ error: 'Invalid signature' });
const event = JSON.parse(req.rawBody);
console.log('Received event:', event.type);
res.status(200).json({ received: true });
});Python
import hmac, hashlib
from flask import Flask, request, jsonify
app = Flask(__name__)
WEBHOOK_SECRET = "whsec_your_secret_here"
def verify_signature(payload: bytes, signature: str, secret: str) -> bool:
expected = hmac.new(secret.encode(), payload, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, signature)
@app.route('/webhooks/brighten', methods=['POST'])
def handle_webhook():
signature = request.headers.get('X-Brighten-Signature', '')
if not verify_signature(request.data, signature, WEBHOOK_SECRET):
return jsonify({"error": "Invalid signature"}), 401
event = request.get_json()
print(f"Received event: {event['type']}")
return jsonify({"received": True}), 200Retry Behavior
If your endpoint returns a non-2xx status code or times out, Brighten will retry with exponential backoff.
| Attempt | Delay | Total Elapsed |
|---|---|---|
| 1st retry | 1 minute | 1 minute |
| 2nd retry | 5 minutes | 6 minutes |
| 3rd retry | 30 minutes | 36 minutes |
| 4th retry | 2 hours | ~2.5 hours |
| 5th retry | 24 hours | ~26.5 hours |
After 5 failed attempts, the webhook will be automatically disabled. Your endpoint must respond within 30 seconds and return a 2xx status code.
Ready to set up webhooks?
Sign up and create your first webhook in minutes.