Platform
ScaiWave ScaiGrid ScaiCore ScaiBot ScaiDrive ScaiKey Models Tools & Services
Solutions
Organisations Developers Internet Service Providers Managed Service Providers AI-in-a-Box
Resources
Support Documentation Blog Downloads
Company
About Research Careers Investment Opportunities Contact
Log in

Webhooks Reference

Endpoints for managing webhook endpoints and the Event Webhook settings. For the guide, see Webhooks.

Base paths:

  • /v3/user/webhooks — endpoint CRUD (multiple URLs, per-URL event set)
  • /v3/user/webhooks/event/settings — single-URL model with per-type booleans

Required permission: webhooks.read for reads, webhooks.write for writes.

GET /v3/user/webhooks#

List webhook endpoints.

Query parameters:

Parameter Notes
page 1-indexed (default 1)
page_size 1–100 (default 20)
is_active Filter by enabled state

Response (200):

json
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
{
  "webhooks": [
    {
      "id": "wh_01HXYZ",
      "url": "https://api.example.com/webhooks/scaisend",
      "enabled_events": ["delivered", "bounce"],
      "enabled": true,
      "created_at": "2026-04-23T10:00:00Z",
      "last_success_at": "2026-04-23T10:15:30Z",
      "last_failure_at": null,
      "failure_count": 0,
      "disabled_at": null
    }
  ],
  "total": 1
}

POST /v3/user/webhooks#

Create a webhook endpoint.

Request body:

Field Type Required Notes
url string Yes HTTPS URL; HTTP is accepted for local development but refused in production
enabled_events array Yes Event types to subscribe to, or ["*"] for all
oauth_client_id string No If set, ScaiSend does OAuth2 before delivering
oauth_client_secret string No Paired with oauth_client_id

Response (201):

json
1
2
3
4
5
6
7
8
{
  "id": "wh_01HXYZ",
  "url": "https://api.example.com/webhooks/scaisend",
  "enabled_events": ["delivered", "bounce"],
  "signing_secret": "whsec_abc123...",
  "enabled": true,
  "created_at": "2026-04-23T10:00:00Z"
}

The signing_secret is returned exactly once. Save it.

GET /v3/user/webhooks/{webhook_id}#

Get a single endpoint.

Response (200): endpoint object (without signing_secret).

PATCH /v3/user/webhooks/{webhook_id}#

Update an endpoint.

Request body: any subset of:

Field Notes
url New URL
enabled_events New event set
enabled Pause/resume delivery

Response (200): updated endpoint.

DELETE /v3/user/webhooks/{webhook_id}#

Delete an endpoint. Past delivery records are also removed.

Response (204): no body.

POST /v3/user/webhooks/{webhook_id}/signing_secret#

Rotate the signing secret.

Response (200):

json
1
{"webhook_id": "wh_01HXYZ", "signing_secret": "whsec_newSecret..."}

The old secret stops validating immediately. Update your verifier before rotating, or accept both briefly during rollout.

GET /v3/user/webhooks/event/settings#

Get the Event Webhook settings (single-URL model).

Response (200):

json
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
{
  "enabled": true,
  "url": "https://api.example.com/webhooks/scaisend",
  "processed": true,
  "deferred": false,
  "delivered": true,
  "bounce": true,
  "dropped": true,
  "open": true,
  "click": true,
  "spam_report": true,
  "unsubscribe": true,
  "group_unsubscribe": true,
  "group_resubscribe": false
}

PATCH /v3/user/webhooks/event/settings#

Update Event Webhook settings. Partial update — unspecified fields retain their value.

Request body: any subset of the response fields above.

Response (200): updated settings.

Webhook request format#

Every outbound request from ScaiSend carries:

Header Value
Content-Type application/json
User-Agent ScaiSend-Webhook/1.0
X-ScaiSend-Event Event type
X-ScaiSend-Timestamp Unix timestamp
X-ScaiSend-Signature HMAC-SHA256 of {timestamp}.{body}

Payload:

json
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
{
  "event_id": "evt_01HXYZ",
  "event_type": "delivered",
  "timestamp": 1713888000,
  "message_id": "msg_01ABC",
  "recipient_email": "user@example.com",
  "tenant_id": "tnt_acme",
  "metadata": {
    "smtp_response": "250 2.0.0 OK"
  }
}

See Events and Webhooks for the per-event metadata fields.

Signature verification#

python
1
2
3
4
5
6
7
8
9
import hmac, hashlib, time

def verify(body: bytes, timestamp: str, signature: str, secret: str) -> bool:
    if abs(time.time() - int(timestamp)) > 300:
        return False
    expected = hmac.new(
        secret.encode(), f"{timestamp}.{body.decode()}".encode(), hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, signature)

Reject requests with a timestamp more than 5 minutes off from wall-clock. That prevents replay attacks.

Retry policy#

ScaiSend retries failed deliveries (any non-2xx response or timeout > 30 seconds) with exponential backoff:

Attempt Delay after previous failure
2 1 minute
3 5 minutes
4 15 minutes
5 1 hour
6 2 hours

After 6 failed attempts, the delivery is marked failed and not retried. After 10 consecutive failures across deliveries, the endpoint is auto-disabled (disabled_at set). Re-enable with PATCH {"enabled": true}.

See Webhooks Deep Dive for the details.

Event types#

Full list of X-ScaiSend-Event values (and the event_type field):

  • processed
  • deferred
  • delivered
  • bounce
  • blocked
  • dropped
  • open
  • click
  • spam_report
  • unsubscribe
  • group_unsubscribe
  • group_resubscribe
Updated 2026-05-17 01:33:27 View source (.md) rev 1