Agent skill

Attentive Webhooks Skill

Receive and verify Attentive webhooks. Use when setting up Attentive webhook handlers, debugging signature verification (x-attentive-hmac-sha256), or handling SMS and email events like sms.subscribed, sms.unsubscribed, email.opened, or custom_attribute.set.

Install this skill

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


When to Use This Skill

  • How do I receive Attentive webhooks?
  • How do I verify Attentive webhook signatures?
  • Why is my x-attentive-hmac-sha256 signature verification failing?
  • How do I handle sms.subscribed, sms.unsubscribed, or email.opened events?
  • Understanding Attentive event types and payloads

Verification (core)

Attentive signs the raw request body with HMAC-SHA256 keyed on your per-webhook signing key (called the "client secret" in the dashboard) and sends the digest, hex-encoded, in the x-attentive-hmac-sha256 header. There is no timestamp in the signature (Attentive does not use the Standard Webhooks scheme), so compute the HMAC over the exact raw body and compare timing-safe. There is no official server-side SDK, so verify manually.

Node:

const crypto = require('crypto');

function verifyAttentiveWebhook(rawBody, signatureHeader, secret) {
  if (!signatureHeader) return false;
  const expected = crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
  try {
    return crypto.timingSafeEqual(
      Buffer.from(signatureHeader, 'hex'),
      Buffer.from(expected, 'hex')
    );
  } catch {
    return false; // wrong length / non-hex input
  }
}

Python:

import hmac, hashlib

def verify_attentive_webhook(raw_body: bytes, signature_header: str, secret: str) -> bool:
    if not signature_header:
        return False
    expected = hmac.new(secret.encode("utf-8"), raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature_header)

For complete handlers with route wiring, event dispatch, and tests, see:

Common Event Types

The event name is in the payload's type field (Attentive does not send an event-type header — only the signature header).

EventTriggered When
sms.subscribedSubscriber joins an SMS list
sms.unsubscribedSubscriber opts out of SMS
sms.sentAn SMS message is sent to a subscriber
sms.inbound_messageA subscriber replies via SMS
sms.message_link_clickSubscriber clicks a link in an SMS
email.subscribedSubscriber joins an email list
email.unsubscribedSubscriber opts out of email
email.sentAn email is sent to a subscriber
email.openedSubscriber opens an email
email.message_link_clickSubscriber clicks a link in an email
custom_attribute.setA custom attribute is set on a subscriber

For the full event reference, see Attentive: Create and manage webhooks.

Payload Structure

{
  "type": "sms.subscribed",
  "timestamp": 1721664000000,
  "company": { "id": "..." },
  "subscriber": { "phone": "+15555550123", "email": "user@example.com" }
}

timestamp is Unix time in milliseconds. Fields present under subscriber vary by event type.

Important Headers

HeaderDescription
x-attentive-hmac-sha256HMAC-SHA256 signature of the raw body, hex-encoded

Environment Variables

ATTENTIVE_WEBHOOK_SECRET=your_signing_key   # "client secret" from the webhook settings

Local Development

# Start tunnel (no account needed)
npx hookdeck-cli listen 3000 attentive --path /webhooks/attentive

Reference Materials


Repository

hookdeck/webhook-skills

v0.1.0 · MIT · Updated Aug 4, 2026

View on GitHub →