SendSure
API Guides
Updated 2026-04-079 min read

Webhooks and API Quickstart

Generate API keys, send your first verification request, and wire webhook consumers with the right operational checks.

Webhooks and API quickstart

SendSure supports two common developer entry points: synchronous verification through the API and asynchronous follow-up through webhooks or downstream processing.

Create an API key

Go to the integrations or API key area in the dashboard and generate a credential dedicated to the workload you are about to build.

Use separate keys for:

  • production traffic
  • staging or QA
  • customer-specific or environment-specific integrations

That separation makes incident response and key rotation much simpler.

Send your first request

Start with one request and verify the shape of the response before you write wrappers around it.

curl -X POST "https://api.sendsure.ai/api/verify/single" \
  -H "Content-Type: application/json" \
  -H "X-API-Key: YOUR_API_KEY" \
  -d '{"email":"alice@example.com"}'

Confirm that your integration handles both high-confidence outcomes and ambiguous ones.

Working in Node.js or Python? Skip the HTTP plumbing entirely — the official SDKs wrap these endpoints with typed clients, automatic rate-limit handling, and the webhook signature helpers described below.

Webhook sanity checks

If you are consuming webhooks:

  1. verify the signature (use the v2 scheme below)
  2. return 2xx quickly
  3. push heavy work into a queue
  4. deduplicate by event id, and log both event IDs and timestamps

Webhook failures are usually caused by slow handlers, incorrect secrets, or endpoints that are not reachable from the public internet.

Webhook delivery headers

Every webhook delivery (including test deliveries) is an HTTP POST with a JSON body and these headers:

| Header | Value | Purpose | | --- | --- | --- | | X-SendSure-Event-Id | UUID, same as the payload id field | Deduplicate deliveries — retries reuse the same id | | X-SendSure-Event | Event type, e.g. job.completed | Route the event without parsing the body | | X-SendSure-Timestamp | Unix seconds at send time | Enforce a replay tolerance window | | X-SendSure-Signature-V2 | t={timestamp},v2={hex} | Recommended. Replay-resistant HMAC-SHA256 signature | | X-SendSure-Signature | sha256={hex} | Legacy HMAC-SHA256 signature of the raw body (kept for backward compatibility) | | Content-Type | application/json | | | User-Agent | SendSure-Webhook/1.0 | |

Verify the v2 signature (recommended)

The v2 signature is computed as HMAC-SHA256(secret, "{timestamp}.{rawBody}"), where timestamp is the t= value from the header and rawBody is the exact raw request body bytes. Because the timestamp is inside the signed message, a captured request cannot be replayed later.

Always verify against the raw body — do not re-serialize parsed JSON.

const crypto = require('crypto');

const TOLERANCE_SECONDS = 300; // reject deliveries older than 5 minutes

function verifyV2(rawBody, signatureHeader, secret) {
  // signatureHeader looks like: t=1751500800,v2=5f8a...
  const parts = Object.fromEntries(
    signatureHeader.split(',').map((kv) => kv.split('=', 2))
  );
  const timestamp = Number(parts.t);
  const signature = parts.v2;
  if (!Number.isFinite(timestamp) || !signature) return false;

  // 1. Replay protection: reject stale (or far-future) timestamps.
  const now = Math.floor(Date.now() / 1000);
  if (Math.abs(now - timestamp) > TOLERANCE_SECONDS) return false;

  // 2. Constant-time signature comparison.
  const expected = crypto
    .createHmac('sha256', secret)
    .update(`${timestamp}.${rawBody}`)
    .digest('hex');
  return (
    expected.length === signature.length &&
    crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature))
  );
}

// Express example — capture the raw body before JSON parsing:
// app.post('/webhooks/sendsure', express.raw({ type: 'application/json' }), (req, res) => {
//   const ok = verifyV2(req.body.toString('utf8'), req.get('X-SendSure-Signature-V2'), SECRET);
//   if (!ok) return res.status(401).end();
//   res.status(200).end(); // ack fast, process async
// });

Verify the legacy signature

The original X-SendSure-Signature header is still sent on every delivery and is unchanged: sha256={hex} where hex = HMAC-SHA256(secret, rawBody). It has no timestamp, so it is not replay-resistant — prefer v2 for new integrations.

const crypto = require('crypto');

function verifyLegacy(rawBody, signatureHeader, secret) {
  const expected =
    'sha256=' + crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
  return (
    expected.length === signatureHeader.length &&
    crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signatureHeader))
  );
}

Idempotent processing

Deliveries are retried with exponential backoff on non-2xx responses, timeouts, and network failures — so your endpoint will occasionally receive the same event more than once. Retries carry the same X-SendSure-Event-Id (and the same id field inside the payload).

Make processing idempotent:

  1. read X-SendSure-Event-Id (or the payload id)
  2. check it against a store of recently processed ids (a unique-keyed table or a Redis SET NX with a TTL of a day works well)
  3. if already seen, return 2xx immediately without reprocessing
  4. otherwise record the id and process the event

Returning 2xx for duplicates is important — a non-2xx response would just trigger more retries of an event you already handled.

Rotating your signing secret

Rotate your secret if it may have leaked, when staff with access leave, or on a regular schedule.

  • Dashboard: Integrations → Webhooks → the "Rotate secret" button next to the secret display.
  • API: POST /api/webhooks/rotate-secret (authenticated). The response contains the new secret exactly once — it cannot be retrieved again.

Rotation is immediate: the next delivery is signed with the new secret, and deliveries signed with the old secret will fail verification. Update the secret in your receiver first/immediately after rotating. If your receiver supports it, accept both secrets for a short overlap window while you roll out the change.

Rate-limit and retry posture

Build for graceful retries. Even if the API is healthy, your own network and downstream services will not be perfect.

Recommended posture:

  • time out client calls intentionally
  • retry idempotent operations
  • log request IDs and relevant status codes
  • isolate verification errors from the rest of your signup flow

What to include in a support request

If you need API help, bring:

  • the endpoint you called
  • approximate timestamp
  • HTTP status code
  • a redacted request sample
  • whether the issue happens in staging or production

That is usually enough for support to narrow the failure domain quickly.

Keep reading

More docs from the api guides section.