Sardis

Webhooks

Receive real-time notifications when events occur in your Sardis account. Signature verification, retry behavior, and event types.

Setup

Register a webhook endpoint to receive event notifications. Your endpoint must be a publicly accessible HTTPS URL.

curl -X POST https://api.sardis.sh/api/v2/webhooks \
  -H "X-API-Key: sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-app.com/sardis-webhook",
    "events": ["payment.completed", "payment.failed", "wallet.frozen"],
    "secret": "<your-webhook-signing-secret>"
  }'

The secret is used to sign webhook payloads so you can verify they came from Sardis. Store it securely — it is only shown once at creation time.

Event Types

EventDescription
payment.completedPayment executed and confirmed on-chain
payment.failedPayment failed (insufficient funds, chain error)
payment.pendingPayment submitted, awaiting confirmation
wallet.createdNew wallet provisioned
wallet.frozenWallet frozen due to policy or manual action
wallet.unfrozenWallet unfrozen and active again
policy.violatedSpending policy violation detected
hold.createdPre-authorization hold placed
hold.capturedHold captured (payment completed)
hold.voidedHold voided (cancelled)
compliance.flaggedTransaction flagged by compliance screening

Payload Schema

All webhook payloads follow this structure:

{
  "event_id": "evt_abc123def456",
  "type": "payment.completed",
  "created_at": "2026-03-24T10:30:00Z",
  "data": {
    "payment_id": "pay_xyz789",
    "wallet_id": "wallet_abc123",
    "amount": "50.00",
    "token": "USDC",
    "chain": "base",
    "tx_hash": "0xabcdef...",
    "status": "success"
  }
}

The event_id is unique per delivery and can be used for idempotent processing. The data field varies by event type.

Signature Verification

Every webhook delivery includes an X-Sardis-Signature header containing an HMAC-SHA256 signature. Always verify signatures before processing events.

Python

import hashlib
import hmac

def verify_webhook(payload: bytes, signature: str, secret: str) -> bool:
    expected = hmac.new(
        secret.encode(),
        payload,
        hashlib.sha256,
    ).hexdigest()
    return hmac.compare_digest(f"sha256={expected}", signature)

# In your webhook handler:
@app.post("/sardis-webhook")
async def handle_webhook(request: Request):
    body = await request.body()
    signature = request.headers.get("X-Sardis-Signature", "")

    if not verify_webhook(body, signature, WEBHOOK_SECRET):
        raise HTTPException(status_code=401, detail="Invalid signature")

    event = json.loads(body)
    # Process event...
    return {"ok": True}

TypeScript

import { createHmac, timingSafeEqual } from "crypto";

function verifyWebhook(
  payload: string,
  signature: string,
  secret: string
): boolean {
  const expected =
    "sha256=" + createHmac("sha256", secret).update(payload).digest("hex");
  return timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
}

Retry Behavior

If your endpoint returns a non-2xx status code or times out, Sardis retries with exponential backoff:

AttemptDelay
1st retry1 minute
2nd retry5 minutes
3rd retry30 minutes
4th retry2 hours
5th retry (final)24 hours

After 5 failed attempts, the delivery is marked as failed. You can view delivery history and manually retry from the dashboard or via the API.

Best Practices

  • Always verify the X-Sardis-Signature header before processing events
  • Use the event_id field for idempotent processing — you may receive the same event more than once
  • Return a 200 response quickly, then process the event asynchronously
  • Store your webhook secret in an environment variable, never in code
  • Use HTTPS endpoints only — HTTP is rejected in production