Agent skill

Synctera Webhooks Skill

Receive and verify Synctera webhooks. Use when setting up Synctera webhook handlers, debugging Synctera-Signature verification, or handling banking events like ACCOUNT.UPDATED or TRANSACTIONS.POSTED.CREATED.

Install this skill

npx skills add hookdeck/webhook-skills --skill synctera-webhooks


When to Use This Skill

  • How do I receive Synctera webhooks?
  • How do I verify Synctera webhook signatures (Synctera-Signature)?
  • How do I handle ACCOUNT.UPDATED or TRANSACTIONS.POSTED.CREATED events?
  • Why is my Synctera webhook signature verification failing?
  • How do I generate a Synctera webhook signing secret?

Verification (core)

Synctera uses a custom HMAC scheme (not Standard Webhooks). Each delivery has two headers:

  • Synctera-Signature — the hex-encoded signature (two .-delimited signatures during secret rotation)
  • Request-Timestamp — POSIX seconds used in the signed string

The signed string is `${Request-Timestamp}.${raw_body}` (the . is a literal separator). Compute HMAC-SHA256(secret, signed_string) and hex-encode. The secret is not your API key — generate it with POST /v0/webhook_secrets (empty body) and store it. Verify against the raw body; don't JSON.parse first.

const crypto = require('crypto');

// secret comes from POST /v0/webhook_secrets (NOT your API key)
function verifySynctera(rawBody, signatureHeader, timestamp, secret, toleranceSec = 300) {
  if (!signatureHeader || !/^\d+$/.test(String(timestamp))) return false;

  // Replay protection: Request-Timestamp must be within 5 minutes of now
  const now = Math.floor(Date.now() / 1000);
  if (Math.abs(now - Number(timestamp)) > toleranceSec) return false;

  // HMAC over `${timestamp}.${rawBody}` — the "." is a literal separator
  const expected = crypto.createHmac('sha256', secret)
    .update(`${timestamp}.${rawBody}`).digest('hex');

  // During a rolling secret, the header holds two "."-delimited signatures
  return signatureHeader.split('.').some((sig) => {
    try { return crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected)); }
    catch { return false; }
  });
}

For complete handlers with route wiring, event dispatch, and tests, see:

Common Event Types

Event names use the format <resource>.[<sub-resource>.]<action>. Wildcards like CUSTOMER.* auto-subscribe to all current and future events under a resource. The three-segment TRANSACTIONS.POSTED.CREATED shows the optional sub-resource case.

Only ACCOUNT.UPDATED and TRANSACTIONS.POSTED.CREATED are verified names. The others below are illustrative of the format only — confirm the exact spelling against Synctera's docs or your own webhook config before subscribing or switching on them. Do not treat this as an authoritative catalog.

EventVerified?Triggered When
ACCOUNT.UPDATED✅ verifiedAn account changed (status, balance limits, etc.)
TRANSACTIONS.POSTED.CREATED✅ verifiedA posted transaction was recorded (note plural TRANSACTIONS, three segments)
CARD.CREATEDillustrativeA card was issued (confirm name)
CARD.UPDATEDillustrativeA card changed — status, activation (confirm name)
DISPUTE.CREATEDillustrativeA dispute was opened (confirm name)
CUSTOMER.*illustrativeAny customer event (wildcard form)

For the full event reference, see references/overview.md and Synctera's Webhooks guide.

Environment Variables

# Signing secret from POST /v0/webhook_secrets (NOT your API key)
SYNCTERA_WEBHOOK_SECRET=your_signature_secret_here

Local Development

# Start tunnel (no account needed)
npx hookdeck-cli listen 3000 synctera --path /webhooks/synctera

Reference Materials


Repository

hookdeck/webhook-skills

v0.1.0 · MIT · Updated Aug 5, 2026

View on GitHub →