Skip to main content
Clawise

Receiving events

A webhook endpoint is just an HTTPS URL that accepts POSTs from Clawise. This page covers registering it, consuming events, and what happens when delivery fails.

Register an endpoint

Decide where in your app the webhook handler will live — for example https://your-app.com/webhooks/clawise. The URL must be HTTPS and publicly reachable.

  1. Open the Webhooks page in the dashboard and click Add endpoint.
  2. Paste that URL and pick the event types you want delivered.
  3. Copy the signing secret. It's only returned on creation — store it like an API key.

Consume events

Build the handler at the URL you registered. The examples below assume your web app is served at your-app.com and mounts the route at /webhooks/clawise — together that's the https://your-app.com/webhooks/clawise URL Clawise POSTs to.

Each POST from Clawise carries three headers:

  • Clawise-Signature-V1 — hex HMAC-SHA256 of {timestamp}.{raw_body} using your signing secret. May contain comma-separated values during secret rotation; accept if any value matches.
  • Clawise-Timestamp — Unix seconds when the delivery was signed. Reject if it's more than a few minutes off your clock.
  • Clawise-Delivery-Id — unique per delivery attempt. Dedup with it if you take side effects.

Use the SDK's webhook verifier — it checks the signature, validates the timestamp, and returns the typed event. No Clawise API key is required — the handler only needs your signing secret. Switch on event_type to dispatch.

import express from "express";
import { constructEvent } from "clawise";

const app = express();

app.post(
  "/webhooks/clawise",
  express.raw({ type: "application/json" }),
  (req, res) => {
    let event;
    try {
      event = constructEvent(
        req.body,
        req.header("Clawise-Signature-V1")!,
        req.header("Clawise-Timestamp")!,
        process.env.CLAWISE_WEBHOOK_SECRET!,
      );
    } catch {
      return res.status(400).end();
    }

    switch (event.event_type) {
      case "mail.message.received":
        onInboundEmail(event.data);
        break;
      case "phone.otp.received":
        onOtp(event.data);
        break;
      case "captcha.task.solved":
        onCaptchaSolved(event.data);
        break;
    }
    res.status(204).end();
  },
);
import os
from flask import Flask, request
from clawise import construct_event

app = Flask(__name__)

@app.post("/webhooks/clawise")
def handle():
    try:
        event = construct_event(
            request.data,
            request.headers["Clawise-Signature-V1"],
            request.headers["Clawise-Timestamp"],
            os.environ["CLAWISE_WEBHOOK_SECRET"],
        )
    except Exception:
        return "", 400

    match event.event_type:
        case "mail.message.received":
            on_inbound_email(event.data)
        case "phone.otp.received":
            on_otp(event.data)
        case "captcha.task.solved":
            on_captcha_solved(event.data)
    return "", 204
import (
    "encoding/json"
    "io"
    "net/http"
    "os"

    clawise "github.com/clawise/clawise-go"
)

func handle(w http.ResponseWriter, r *http.Request) {
    body, _ := io.ReadAll(r.Body)

    if err := clawise.VerifyWebhookSignature(
        body,
        r.Header.Get("Clawise-Signature-V1"),
        r.Header.Get("Clawise-Timestamp"),
        os.Getenv("CLAWISE_WEBHOOK_SECRET"),
    ); err != nil {
        http.Error(w, "", http.StatusBadRequest)
        return
    }

    var event clawise.Event
    if err := json.Unmarshal(body, &event); err != nil {
        http.Error(w, "", http.StatusBadRequest)
        return
    }

    switch event.Type {
    case "mail.message.received":
    case "phone.otp.received":
    case "captcha.task.solved":
    }
    w.WriteHeader(http.StatusNoContent)
}

See Event types for the full list and payloads.

Retry behavior

Clawise considers a delivery successful when your endpoint returns a 2xx within 5 seconds. Anything else — non-2xx, timeout, connection failure — triggers retries.

AttemptDelay from previous
1 (initial)—
230 seconds
32 minutes
410 minutes
51 hour
66 hours

Each interval gets up to 25% random jitter. After the 6th attempt fails, the delivery is marked failed and not retried again. An endpoint that fails 5 consecutive deliveries is auto-disabled — re-enable it from the dashboard once you've fixed it.

Because retries are possible, dedup on Clawise-Delivery-Id if you take side effects from the event.

On this page