Agent skill

Community Webhooks Skill

Receive and verify Community (community.com) SMS platform webhooks. Use when setting up a Community webhook handler, debugging `community-signature` HMAC-SHA256 verification, or handling message.inbound, message.outbound, member.created, member.updated, and member.deleted events.

Install this skill

npx skills add hookdeck/webhook-skills --skill community-webhooks


Webhooks from Community (community.com) — the SMS / conversational-messaging platform for brands and creators. This is not a generic community-forum product, not Circle/Discourse/Bettermode, and not Salesforce Experience Cloud. The developer hub is https://developer.community.com.

When to Use This Skill

  • How do I receive Community webhooks?
  • How do I verify the community-signature header?
  • Why is my Community webhook signature verification failing?
  • How do I handle message.inbound / message.outbound events?
  • How do I handle member.created, member.updated, and member.deleted events?
  • How do I deduplicate Community webhooks (at-least-once delivery)?

Verification (core)

Community signs every request with HMAC-SHA256 over {timestamp}.{raw_body} and sends the result in a Stripe-style community-signature header:

community-signature: t=1711666033,v1=b777f6ae2497ae95e99811c88b28d8ba377c95d615905963c68fae4c800de48d

The HMAC key is the signature secret, which is unique to each webhook (Dashboard → Settings → Integrations → Webhooks). It is not your community_api-prefixed Async REST API token. Always HMAC the raw request body — re-serializing the JSON changes the bytes and breaks the signature.

const crypto = require('crypto');

function verifyCommunitySignature(rawBody, signatureHeader, secret) {
  if (!signatureHeader || !secret) return false;

  // Parse "t=...,v1=..." without assuming field order
  const fields = Object.fromEntries(
    signatureHeader.split(',').map((p) => {
      const i = p.indexOf('=');
      return i === -1 ? ['', ''] : [p.slice(0, i).trim(), p.slice(i + 1).trim()];
    })
  );
  const timestamp = fields.t;
  const signature = fields.v1; // only the v1 scheme is supported
  if (!timestamp || !signature) return false;

  // Signed content is: timestamp + "." + raw body
  const expected = crypto
    .createHmac('sha256', secret)
    .update(`${timestamp}.${rawBody}`, 'utf8')
    .digest('hex');

  try {
    return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
  } catch {
    return false; // length mismatch
  }
}

For complete handlers with tests, see examples/express/, examples/nextjs/, examples/fastapi/.

There is no official Community SDK for webhook verification in any language — implement the HMAC directly, as above.

Event Types

Community documents exactly five event types. Subscriptions are selected per webhook in the Dashboard.

EventTriggered Whenobject
message.inboundA member sends a message to your accountmessage
message.outboundYour account sends a message to a member (some are filtered out)message
member.createdA new member joinsmember
member.updatedA member changes any standard personal data collectedmember
member.deletedA member unsubscribes or deletes themselvesmember

Payload Envelope

{
  "id": "4fab65b1-c98f-466e-b52f-c170768a6d89",
  "type": "member.created",
  "object": "member",
  "created": "2025-01-05T23:59:45.643131Z",
  "api_version": "2024-02-12",
  "data": { "object": { "id": "7a3e02ec-...", "active": true } }
}

Event data lives at data.object in every documented sample payload. The prose in the same doc page says member events "include a member object in the event's data field", so handlers should read data.object first and fall back to data.member / data.message defensively.

member.deleted payloads are sparse — only id, active: false, timestamp, client_id, communication_channel, and an emptied communication_channel_id. Never assume email, given_name, geolocation, or any other personal-data field is present on a member event.

Delivery Semantics

  • At-least-once. The same event can be delivered more than once. Deduplicate by storing the webhook id (or object id) for at least an hour and checking it before processing. This matters most for messages — prefer at-most-once handling there.
  • Respond 2xx (200–299) within 15 seconds. The response body is ignored.
  • Retries: on a connection error, non-2xx, or timeout, Community retries up to 5 times with increasing backoff, for up to an hour from the first attempt. Continued failures trigger a notification email and the webhook may be disabled (and must then be re-enabled).
  • So: verify, enqueue, return 2xx fast — never do the work inline.

Important Headers

HeaderDescription
community-signaturet=<unix_seconds>,v1=<hex> — HMAC-SHA256 of {t}.{raw_body}, lowercase hex

There is no documented source-IP allowlist, no handshake, and no subscription-confirmation request. Community's other transport safeguards are that only HTTPS endpoints are accepted and SSL certificates must be valid for the correct host.

Environment Variables

COMMUNITY_WEBHOOK_SECRET=your_signature_secret   # Dashboard → Settings → Integrations → Webhooks (per webhook)

# Optional hardening (NOT a documented Community requirement — see below)
COMMUNITY_WEBHOOK_TOLERANCE_SECONDS=0            # 0 = disabled (default)

The docs specify no tolerance window for t. A replay/staleness check is therefore the implementer's own hardening choice, and it is disabled by default in the examples. If you enable one, make the window comfortably larger than an hour — retries of a failed delivery can arrive up to an hour after the first attempt.

Local Development

# Forward Community webhooks to your local server (no account required)
npx hookdeck-cli listen 3000 community --path /webhooks/community

Paste the resulting HTTPS URL into the endpoint URL field of your webhook in Dashboard → SettingsIntegrationsWebhooks.

Access

Webhooks are a plan/permission-gated Community feature and can only be configured in the Community Dashboard — there is no API to create them. Contact yourfriends@community.com to arrange access.

Reference Materials


Repository

hookdeck/webhook-skills

v0.1.0 · MIT · Updated Aug 27, 2026

View on GitHub →