TweetsmashDocs
LoginDashboard
Getting Started
Getting Started
For AI agents
OverviewConnect your agentAgent Skills
MCP
MCP Integration
Bookmarks
GETFetch Saved PostsGETGet a Bookmark (full content)
Labels
GETManage LabelsPOSTAdd Labels to TweetsPOSTRemove Labels from TweetsPOSTAdd Labels by Filter (bulk)
Smart Views
GETList Smart ViewsGETGet / Update / Delete a Smart ViewGETGet Smart View Bookmarks
Exports
POSTExport Bookmarks (PDF / CSV / JSON)
Memory
GETMemories (remember / recall / forget)
Integrations
n8n IntegrationWebhook Integration
API Docs/Automations/Webhooks
Automations

Webhook IntegrationBeta

Receive real-time notifications when your bookmarks or labels change. Configure a webhook URL, choose events, and verify signed payloads for security.

Webhooks are a paid feature. Create and manage endpoints from Integrations → Webhook.

How it works

Register an HTTPS endpoint and pick the events to subscribe to. When a subscribed event fires, Tweetsmash POSTs a signed JSON payload to your URL so you can verify it's authentic. Failed deliveries (5xx or network errors) are retried with exponential backoff.

1

Create an endpoint

Add your HTTPS URL and choose events. A secret token is generated and shown once on creation — store it securely to verify signatures.

Open Webhooks
2

Receive & verify

Your endpoint receives one POST per event. Verify the signature header before trusting the body: X-Webhook-Signature

3

Act on the event

Use the event type and data to drive your workflow — sync to a database, notify a channel, or kick off an automation.

Subscribable Events

Subscribe to any combination of these events per endpoint.

Saved bookmarks

bookmarks.imported
Bookmarks Imported
bookmarks.read
Bookmark Marked as Read
bookmarks.unread
Bookmark Marked as Unread
bookmarks.archived
Bookmark Archived
bookmarks.unarchived
Bookmark Unarchived
bookmarks.deleted
Bookmark Deleted

Labels

bookmarks.labels.added
Label Added to Bookmark
bookmarks.labels.deleted
Label Removed from Bookmark

Schedules

schedules.triggered
Schedule Triggered

Payload Structure

All events share a common envelope.

JSON
{
  "id": "012cf7ea-35ec-47e3-a368-6018c2a22199",
  "event": "bookmarks.unread",
  "event_ids": ["1869409110774763597"],
  "timestamp": "2025-08-08T15:20:13.011Z",
  "data": [
    { "post_id": "1869409110774763597" }
  ],
  "webhook_id": "8607439c-1270-42d9-a9d8-907dd91b8b10"
}

Headers sent with every delivery:

HTTP
POST /your/webhook/url HTTP/1.1
Content-Type: application/json
User-Agent: TweetsMash-Webhook/1.0
X-Webhook-Signature: sha256=<hex-digest>
X-Webhook-Timestamp: 2025-08-08T15:20:13.011Z

Example Payloads

JSON
{
  "id": "2c7bbc6a-34f7-49c9-a8b0-782036c1b989",
  "event": "bookmarks.imported",
  "event_ids": [
    "1953471271414714463",
    "1953398302742872144",
    "1953597493649736020"
  ],
  "timestamp": "2025-08-08T15:29:20.341Z",
  "data": [
    {
      "post_id": "1953471271414714463",
      "tweet_details": {
        "text": "As a product designer, I love cool design effects.\n\nSo I analyzed @Apple's 3D design revolution.",
        "link": "https://twitter.com/DenisJeliazkov/status/1953471271414714463",
        "posted_at": "Thu Aug 07 15:00:19 +0000 2025"
      },
      "author_id": "711232574245498880",
      "author_username": "DenisJeliazkov",
      "posted_at": "Thu Aug 07 15:00:19 +0000 2025",
      "sort_index": "1839901608806183041"
    }
  ],
  "webhook_id": "8607439c-1270-42d9-a9d8-907dd91b8b10"
}

Verify Signature

Each request is signed with HMAC-SHA256 over the raw request body using your webhook secret. Recompute it and compare in constant time. Optionally reject deliveries whose timestamp is older than 5 minutes.

Node.js
import crypto from 'crypto';

function timingSafeEqual(a, b) {
  const aBuf = Buffer.from(a);
  const bBuf = Buffer.from(b);
  if (aBuf.length !== bBuf.length) return false;
  return crypto.timingSafeEqual(aBuf, bBuf);
}

export function verifyTweetsMashSignature(rawBody, headers, secret) {
  const sigHeader = headers['x-webhook-signature'] || headers['X-Webhook-Signature'];
  if (!sigHeader || !sigHeader.startsWith('sha256=')) return false;
  const received = sigHeader.slice('sha256='.length);
  const computed = crypto.createHmac('sha256', secret).update(rawBody).digest('hex');

  // Optional timestamp freshness check
  const ts = headers['x-webhook-timestamp'] || headers['X-Webhook-Timestamp'];
  if (ts) {
    const ageMs = Math.abs(Date.now() - Date.parse(ts));
    if (ageMs > 5 * 60 * 1000) return false;
  }
  return timingSafeEqual(computed, received);
}
Python
import hmac, hashlib
from datetime import datetime, timezone

def verify_tweetsmash_signature(raw_body: bytes, headers: dict, secret: str) -> bool:
    sig_header = headers.get('x-webhook-signature') or headers.get('X-Webhook-Signature')
    if not sig_header or not sig_header.startswith('sha256='):
        return False
    received = sig_header[len('sha256='):]
    computed = hmac.new(secret.encode('utf-8'), raw_body, hashlib.sha256).hexdigest()

    ts = headers.get('x-webhook-timestamp') or headers.get('X-Webhook-Timestamp')
    if ts:
        try:
            age = abs((datetime.now(timezone.utc)
                       - datetime.fromisoformat(ts.replace('Z', '+00:00'))).total_seconds())
            if age > 300:
                return False
        except Exception:
            return False

    return hmac.compare_digest(computed, received)
Verify against the raw body before parsing JSON — re-serializing can change the bytes and break the check.

Delivery & Security

Respond with a 2xx to acknowledge. On a 5xx or network error we retry up to 3 times with exponential backoff (~1, 5, 15 minutes). A 4xx is treated as permanent and not retried. Recent deliveries (status and response code) are visible on each webhook's settings.

Status
Meaning
2xx
Acknowledged — the delivery is marked successful.
4xx
Permanent failure — the delivery is not retried.
5xx
Temporary failure — retried up to 3 times (~1, 5, 15 minutes).

Best practices

  • Always use HTTPS and verify every signature.
  • Treat the signing secret like a password; rotate by recreating the webhook.
  • Return fast (within a few seconds) and do heavy work asynchronously.
  • Make handlers idempotent — the same event may be retried.