API Reference
Complete REST API documentation for SendMesh. All endpoints require authentication via Bearer token.
Authentication
Authorization: Bearer sk_live_xxxInclude your secret API key in the Authorization header of every request.
Rate Limits
Varies by plan. Check response headers:
X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-ResetBase URL
All API requests use this base URL
https://api.sendmesh.co/v1Response Format
All responses follow a consistent envelope:
{
"data": { ... },
"error": null,
"meta": {
"requestId": "req_abc123",
"timestamp": "2026-04-14T10:30:00Z"
}
}
// Error response:
{
"data": null,
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid email address format",
"details": [{ "field": "to[0].email", "message": "Must be a valid email" }]
},
"meta": { "requestId": "req_abc123", "timestamp": "2026-04-14T10:30:00Z" }
}Pagination
All list endpoints support cursor-based pagination:
curl "https://api.sendmesh.co/v1/contacts?cursor=cur_abc123&limit=50" \
-H "Authorization: Bearer sk_live_your_key"
# Response meta includes:
# "meta": { "total": 5420, "cursor": "cur_next456", "hasMore": true }Webhook Security
Every webhook payload includes a signature for verification. Always verify signatures before processing webhook events to ensure they originate from SendMesh.
Signature Headers
X-SendMesh-SignatureHMAC-SHA256 hex digest of the raw request body, computed with your webhook secret.X-SendMesh-TimestampUnix timestamp (seconds) of when the webhook was sent. Use for replay attack prevention.How Verification Works
1. Concatenate the timestamp and raw body: {timestamp}.{body}
2. Compute HMAC-SHA256 using your webhook secret as the key
3. Compare the computed signature with X-SendMesh-Signature using a timing-safe comparison
4. Reject if the timestamp is more than 5 minutes old (replay attack prevention)
Node.js
const crypto = require('crypto');
function verifyWebhookSignature(req, secret) {
const signature = req.headers['x-sendmesh-signature'];
const timestamp = req.headers['x-sendmesh-timestamp'];
// Reject if timestamp is older than 5 minutes (replay prevention)
const age = Math.floor(Date.now() / 1000) - parseInt(timestamp, 10);
if (Math.abs(age) > 300) {
throw new Error('Webhook timestamp too old — possible replay attack');
}
// Compute expected signature
const payload = `${timestamp}.${req.body}`;
const expected = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
// Timing-safe comparison
const sig = Buffer.from(signature, 'hex');
const exp = Buffer.from(expected, 'hex');
if (sig.length !== exp.length || !crypto.timingSafeEqual(sig, exp)) {
throw new Error('Invalid webhook signature');
}
return true; // Signature verified
}
// Express.js usage:
app.post('/webhooks/sendmesh', express.raw({ type: 'application/json' }), (req, res) => {
try {
verifyWebhookSignature(req, 'whsec_your_signing_secret');
const event = JSON.parse(req.body);
// Process the event...
res.status(200).send('OK');
} catch (err) {
res.status(401).send('Unauthorized');
}
});Python
import hmac
import hashlib
import time
def verify_webhook_signature(payload: bytes, signature: str, timestamp: str, secret: str) -> bool:
# Reject if timestamp is older than 5 minutes
age = abs(int(time.time()) - int(timestamp))
if age > 300:
raise ValueError("Webhook timestamp too old — possible replay attack")
# Compute expected signature
message = f"{timestamp}.{payload.decode('utf-8')}"
expected = hmac.new(
secret.encode("utf-8"),
message.encode("utf-8"),
hashlib.sha256,
).hexdigest()
# Timing-safe comparison
if not hmac.compare_digest(signature, expected):
raise ValueError("Invalid webhook signature")
return True
# Flask usage:
@app.route("/webhooks/sendmesh", methods=["POST"])
def handle_webhook():
try:
verify_webhook_signature(
payload=request.data,
signature=request.headers["X-SendMesh-Signature"],
timestamp=request.headers["X-SendMesh-Timestamp"],
secret="whsec_your_signing_secret",
)
event = request.get_json()
# Process the event...
return "OK", 200
except ValueError:
return "Unauthorized", 401PHP
<?php
function verifyWebhookSignature(string $payload, string $signature, string $timestamp, string $secret): bool
{
// Reject if timestamp is older than 5 minutes
$age = abs(time() - (int) $timestamp);
if ($age > 300) {
throw new Exception('Webhook timestamp too old — possible replay attack');
}
// Compute expected signature
$message = "{$timestamp}.{$payload}";
$expected = hash_hmac('sha256', $message, $secret);
// Timing-safe comparison
if (!hash_equals($expected, $signature)) {
throw new Exception('Invalid webhook signature');
}
return true;
}
// Usage:
$payload = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_SENDMESH_SIGNATURE'] ?? '';
$timestamp = $_SERVER['HTTP_X_SENDMESH_TIMESTAMP'] ?? '';
try {
verifyWebhookSignature($payload, $signature, $timestamp, 'whsec_your_signing_secret');
$event = json_decode($payload, true);
// Process the event...
http_response_code(200);
echo 'OK';
} catch (Exception $e) {
http_response_code(401);
echo 'Unauthorized';
}Replay Attack Prevention
Always check the X-SendMesh-Timestamp header. Reject any webhook where the timestamp is more than 5 minutes from the current time. This prevents attackers from capturing a valid payload and replaying it later. The signature includes the timestamp, so an attacker cannot modify it without invalidating the signature.
Prefer using an SDK?
Official libraries for Node.js, Python, and PHP.