Webhooks¶
Webhooks let your systems react in real time when resources change in PlusPlus — no polling required.
Preview
Webhooks are designed but not yet implemented. This page describes the planned interface so you can design integrations against it. The API surface may change before launch.
How webhooks work¶
- You register a subscription with a target URL, an HMAC secret, and a list of event types.
- When a matching event fires, PlusPlus
POSTs a JSON payload to your URL with anX-Plusplus-Signatureheader. - Your endpoint verifies the signature, processes the event, and returns
2xx. - PlusPlus retries failures with exponential backoff for ~24 hours, then marks the delivery
dead.
Event taxonomy¶
Naming follows {resource}.{action} (Stripe/GitHub-style).
Generic content events¶
Fired for every content type. Payload uses the shared ContentItemSummary schema.
| Event | When |
|---|---|
content_item.created |
Any content item is created. |
content_item.updated |
Any content item's shared metadata changes. |
content_item.deleted |
Any content item is hard-deleted. |
content_item.archived |
Any content item is archived (soft-delete). |
content_item.unarchived |
Any content item is restored from archive. |
Type-specific events¶
Fired alongside the generic event, with a richer, type-specific payload.
| Type | Event prefix | Actions |
|---|---|---|
| Article | article.* |
created, updated, deleted, archived, unarchived |
| Video | video.* |
created, updated, deleted, archived, unarchived |
| Course | course.* |
created, updated, deleted, archived, unarchived |
| Guide | guide.* |
created, updated, deleted, archived, unarchived |
| Event | event.* |
created, updated, deleted, archived, unarchived |
| Event Type | event_type.* |
created, updated, deleted, archived, unarchived |
| Link | link.* |
created, updated, deleted, archived, unarchived |
| Track | track.* |
created, updated, deleted, archived, unarchived |
| Scheduled Track | scheduled_track.* |
created, updated, deleted, archived, unarchived |
| Collection | collection.* |
created, updated, deleted, archived, unarchived |
When an article is created, both content_item.created and article.created fire. Subscribe to whichever fits your needs (or both — events are deduplicated by id).
Assignment events¶
| Event | When |
|---|---|
assignment.created |
A user is assigned content. |
assignment.updated |
Assignment metadata changes. |
assignment.completed |
A user marks an assignment complete. |
assignment.dropped |
A user drops an assignment. |
assignment.deleted |
An assignment is removed. |
Wildcard subscriptions¶
You can subscribe with fnmatch-style globs:
*— all eventscontent_item.*— all generic content eventsarticle.*— all article-specific eventsassignment.*— all assignment events
Payload format¶
{
"id": "evt_a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"type": "article.created",
"api_version": "2.0.0",
"created_at": "2026-04-06T12:00:00Z",
"data": {
"object": { /* same shape as GET response for that resource */ }
}
}
data.object is identical to the GET response for that resource — no special parsing required.
Signature verification¶
Each delivery includes:
X-Plusplus-Signature: t=1714473600,v1=<hex_hmac>
X-Plusplus-Event: article.created
X-Plusplus-Event-Id: evt_a1b2c3d4-...
Verify the signature by computing HMAC-SHA256(secret, "{t}.{raw_body}") and comparing with v1 using a constant-time comparator. Reject deliveries where t is more than five minutes old to prevent replay.
Delivery semantics¶
- At-least-once. Use
idto deduplicate. - Retries. Exponential backoff up to ~24 hours total.
- Order. Deliveries are not strictly ordered. Use
created_atif order matters. - Timeout. Your endpoint must return
2xxwithin 10 seconds.
Security¶
- Target URLs must be
https://. - Private/internal IP ranges (
10.x,172.16.x,192.168.x,127.x,localhost) are rejected at registration. - The HMAC secret is shown once at creation and rotation. Store it in a secret manager.
Subscription API¶
| Method | Path | Description |
|---|---|---|
GET |
/webhooks |
List subscriptions. |
POST |
/webhooks |
Create a subscription (returns secret once). |
GET |
/webhooks/{public_id} |
Get a subscription. |
PATCH |
/webhooks/{public_id} |
Update url, events, description, or is_active. |
DELETE |
/webhooks/{public_id} |
Delete a subscription. |
POST |
/webhooks/{public_id}/test |
Send a ping event to verify your endpoint. |
POST |
/webhooks/{public_id}/rotate-secret |
Generate a new secret (returns once). |
GET |
/webhooks/{public_id}/deliveries |
List recent delivery attempts. |
See the API reference for full request/response schemas once these endpoints ship.