Webhook IntegrationBeta
Receive real-time notifications when your bookmarks or labels change. Configure a webhook URL, choose events, and verify signed payloads for security.
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.
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.
Receive & verify
Your endpoint receives one POST per event. Verify the signature header before trusting the body: X-Webhook-Signature
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
Labels
Schedules
Payload Structure
All events share a common envelope.
{
"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:
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.011ZExample Payloads
{
"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.
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);
}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)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.
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.