Skip to content
JEE Main & JEE Advanced datasets — Coming Soon

Webhooks

Receive real-time notifications when events happen in your PaperStack account.

Webhooks

Webhooks allow you to receive real-time HTTP notifications when events occur in your PaperStack account — like a key being created, revoked, or a billing status change.

How It Works

  1. You register a webhook endpoint (an HTTPS URL) in your Dashboard
  2. PaperStack sends a POST request to your endpoint when an event fires
  3. Your server responds with 200 OK within 10 seconds to acknowledge receipt
  4. If delivery fails, we retry up to 3 times with exponential backoff (5s, 25s, 125s)

Supported Events

EventDescription
key.createdA new API key was created
key.revokedAn existing API key was revoked
key.expiredA key reached its expiration date
usage.quota_80You've used 80% of your monthly quota
usage.quota_100You've hit your monthly limit
billing.plan_upgradedYour plan was upgraded
billing.plan_downgradedYour plan was downgraded
billing.payment_failedA payment attempt failed
billing.subscription_canceledYour subscription ended

Registering a Webhook

  1. Go to your Dashboard
  2. Click Add Webhook
  3. Enter your HTTPS endpoint URL
  4. Select the events you want to receive
  5. Save — PaperStack generates a unique HMAC signing secret

Verifying Delivery (Signature)

Every webhook request includes an X-PaperStack-Signature header:

X-PaperStack-Signature: sha256=abc123...

Verify the signature using your webhook's secret:

import crypto from 'crypto';

function verifySignature(payload, signature, secret) {
  const expected = 'sha256=' + crypto
    .createHmac('sha256', secret)
    .update(JSON.stringify(payload))
    .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );
}
import hmac
import hashlib

def verify_signature(payload, signature, secret):
    expected = 'sha256=' + hmac.new(
        secret.encode(),
        json.dumps(payload, separators=(',', ':')).encode(),
        hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(signature, expected)
import (
  "crypto/hmac"
  "crypto/sha256"
  "encoding/hex"
  "encoding/json"
)

func verifySignature(payload interface{}, signature string, secret string) bool {
  data, _ := json.Marshal(payload)
  mac := hmac.New(sha256.New, []byte(secret))
  mac.Write(data)
  expected := "sha256=" + hex.EncodeToString(mac.Sum(nil))
  return hmac.Equal([]byte(signature), []byte(expected))
}

Payload Format

Every webhook request sends the following JSON body:

{
  "id": "unique-delivery-id",
  "event": "key.created",
  "timestamp": "2026-05-28T12:00:00.000Z",
  "data": {
    "keyId": "key_xxx",
    "name": "Production Key"
  }
}

Delivery Headers

HeaderDescription
X-PaperStack-SignatureHMAC-SHA256 signature of the payload
X-PaperStack-EventThe event type (e.g. key.created)
X-PaperStack-DeliveryUnique delivery ID for idempotency

Best Practices

  • Respond with 200 OK as quickly as possible — process asynchronously
  • Verify the signature before trusting the payload
  • Use idempotency keys — the X-PaperStack-Delivery header is unique per attempt
  • Store the secret securely — it's shown only once when you create the webhook
  • Rotate secrets periodically via the dashboard

Delivery Log

Your Dashboard shows a delivery log with:

  • Delivery status (success/failure)
  • HTTP status code
  • Response body (first 1KB)
  • Duration
  • Attempt number

Failed deliveries are retried 3 times. If a webhook fails 10+ consecutive times, it is automatically marked as failing.

Was this page helpful?

On this page