Get notified instantly when things happen in your community. HMAC-signed payloads, automatic retries with exponential backoff, and full delivery logs.
Webhooks send HTTP POST requests to your server when events occur in your community. Create them from Admin → Developer API → Webhooks tab.
Every webhook delivery is a JSON POST request with a consistent envelope structure.
{
"event": "member.joined",
"timestamp": "2026-03-28T14:30:00Z",
"communityId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"data": {
"memberId": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
"displayName": "Jane Smith",
"email": "jane@example.com",
"planName": "Pro Monthly"
}
}| Field | Type | Description |
|---|---|---|
| event | string | The event type in dotted notation (e.g. member.joined) |
| timestamp | string | ISO 8601 UTC timestamp of when the event occurred |
| communityId | string | GUID of the community where the event occurred |
| data | object | Event-specific data — varies by event type |
Every webhook delivery includes these custom headers for verification and debugging.
sha256={hex_digest}member.joined)application/jsonMemberPad-Webhook/1.0Always verify the X-MemberPad-Signature header to ensure the payload was sent by MemberPad and hasn't been tampered with. Compute the HMAC-SHA256 of the raw request body using your webhook's signing secret and compare.
import crypto from "crypto";
import express from "express";
const SECRET = "whsec_your_signing_secret";
const app = express();
// Use raw body for accurate HMAC computation
app.use(express.json({
verify: (req, res, buf) => { req.rawBody = buf; }
}));
app.post("/webhooks/memberpad", (req, res) => {
const signature = req.headers["x-memberpad-signature"];
const expected = "sha256=" + crypto
.createHmac("sha256", SECRET)
.update(req.rawBody)
.digest("hex");
if (!crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
)) {
return res.status(401).send("Invalid signature");
}
// Signature valid — process the event
const { event, data } = req.body;
console.log(`Received: ${event}`, data);
// Return 200 quickly — do heavy work asynchronously
res.status(200).send("OK");
});
app.listen(3000);timingSafeEqual (Node.js), hmac.compare_digest (Python), or constant-time comparison to prevent timing attacks.If your endpoint returns a non-2xx status code, times out (10 seconds), or is unreachable, MemberPad will retry the delivery with exponential backoff.
After 5 failed attempts, the delivery is marked as Exhausted. You can manually retry any failed or exhausted delivery from the admin panel at any time.
| Spec | Value | |
|---|---|---|
| Max attempts | 5 | |
| Timeout | 10 seconds per attempt | |
| Success | Any 2xx HTTP status code | |
| Failure | Non-2xx status, timeout, or connection error | |
| Redirects | Not followed (returns the redirect status as a failure) |
X-MemberPad-Delivery header (unique per delivery) to deduplicate if a retry succeeds after a timeout.22 event types across 6 categories. Subscribe to all events or select specific ones when creating a webhook.
| Event | Description |
|---|---|
| Membership | |
| member.joined | A new member joined the community |
| member.left | A member left or was removed from the community |
| member.role_changed | A member's role was changed (e.g., promoted to Admin) |
| Subscriptions | |
| subscription.created | A new subscription was started |
| subscription.cancelled | A subscription was cancelled |
| subscription.renewed | A subscription was successfully renewed |
| subscription.plan_changed | A member upgraded or downgraded their plan |
| Payments | |
| payment.succeeded | A payment was successfully processed |
| payment.failed | A payment attempt failed |
| payment.refund_issued | A refund was issued |
| Content | |
| post.published | A new post was published |
| post.updated | An existing post was updated |
| course.published | A new course was published |
| course.lesson_completed | A member completed a course lesson |
| course.completed | A member completed an entire course |
| event.created | A new event was created |
| event.updated | An event was updated |
| Commerce | |
| product.purchased | A digital product was purchased |
| Engagement | |
| comment.created | A new comment was posted on a post |
| chat.message_posted | A message was posted in a chat room |
| Support | |
| support_ticket.created | A new support ticket was submitted |
| feature_request.created | A new feature request was submitted |