Webhooks
Receive real-time notifications about events in your Caring CourseForge account
Webhooks allow you to receive real-time HTTP notifications when events occur in your account, eliminating the need for polling and enabling responsive integrations.
Available Events
Course Events
course.created
- New course createdcourse.updated
- Course details modifiedcourse.published
- Course published/unpublishedcourse.deleted
- Course deleted
Student Events
student.enrolled
- Student enrolled in coursestudent.completed_lesson
- Lesson completedstudent.completed_course
- Course completedstudent.unenrolled
- Student unenrolled
Assessment Events
quiz.submitted
- Quiz submittedquiz.passed
- Quiz passedquiz.failed
- Quiz failed
System Events
export.completed
- Course export finishedexport.failed
- Export failed
Creating a Webhook
Setup Steps
- Go to Settings → Webhooks
- Click "Create Webhook"
- Enter your endpoint URL (must be HTTPS)
- Select events you want to receive
- Optionally set a custom secret for signature verification
- Click "Create"
- Test with "Send Test Event"
Webhook Payload
Standard Payload Structure
{ "id": "evt_1234567890", "type": "student.completed_course", "created": 1633024800, "data": { "student_id": "stu_abc123", "student_email": "student@example.com", "course_id": "course_xyz789", "course_title": "Introduction to Python", "completed_at": "2025-10-11T14:30:00Z", "final_score": 92.5, "time_spent_minutes": 480 }, "account_id": "acct_demo" }
Verifying Webhook Signatures
Verify that webhooks are from Caring CourseForge and haven't been tampered with:
Signature Header
Each webhook includes a signature in the X-CCF-Signature
header
X-CCF-Signature: t=1633024800,v1=5257a869e7ecebeda32...
Node.js Verification
const crypto = require('crypto'); function verifyWebhook(payload, signature, secret) { const [timestamp, hash] = signature.split(',') .map(part => part.split('=')[1]); const signedPayload = `${timestamp}.${JSON.stringify(payload)}`; const expectedHash = crypto .createHmac('sha256', secret) .update(signedPayload) .digest('hex'); return crypto.timingSafeEqual( Buffer.from(hash), Buffer.from(expectedHash) ); }
Python Verification
import hmac import hashlib import json def verify_webhook(payload, signature, secret): parts = dict(part.split('=') for part in signature.split(',')) timestamp = parts['t'] received_hash = parts['v1'] signed_payload = f"{timestamp}.{json.dumps(payload)}" expected_hash = hmac.new( secret.encode(), signed_payload.encode(), hashlib.sha256 ).hexdigest() return hmac.compare_digest(received_hash, expected_hash)
Handling Webhooks
- Return 200 OK as quickly as possible (within 5 seconds)
- Process webhook data asynchronously (use queues)
- Implement idempotency (same event may be sent multiple times)
- Always verify signatures
- Log all webhook events for debugging
- Handle duplicate events gracefully
Retry Logic
If your endpoint doesn't return 200-299, we'll retry:
- Immediately
- After 5 minutes
- After 30 minutes
- After 2 hours
- After 6 hours
After 5 consecutive failures, the webhook is automatically disabled. You'll receive an email notification.
Testing Webhooks
Local Development
Use tools like ngrok to test webhooks locally:
Monitoring Webhooks
View webhook activity in your dashboard:
- All webhook deliveries (success/failed)
- Response status codes
- Response times
- Retry attempts
- Payload and response body
Common Issues
Endpoint Timing Out
Solution: Return 200 immediately, then process asynchronously. Don't perform long-running tasks in the webhook handler.
Receiving Duplicate Events
Solution: Use the event id
to implement idempotency. Store processed event IDs and skip duplicates.
Webhook Disabled Unexpectedly
Solution: Check webhook logs in dashboard. Ensure your endpoint returns 2xx status and responds within 5 seconds.
Rate Limits
Webhooks are not subject to API rate limits, but there are delivery limits: