Skip to main content
Docs/API Reference

API Reference

Complete REST API documentation for SendMesh. All endpoints require authentication via Bearer token.

Authentication

Authorization: Bearer sk_live_xxx

Include 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-Reset

Base URL

All API requests use this base URL

https://api.sendmesh.co/v1

Response Format

All responses follow a consistent envelope:

json
{
  "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
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

javascript
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

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", 401

PHP

php
<?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.

View SDKs