Connector

Inbound signed calls from Supdesk to your backend: user context, custom actions, and refunds.

Configure connector_base_url and the generated connector_secret in the dashboard. Every request is POST {base}{path} with a JSON body and X-Supportal-Signature header. Timeout 10s; responses ≤ 32KB.

Request body

json
{
  "requestId": "req_abc",
  "tenant": "acme",
  "user": { "id": "user_123" },
  "conversationId": "conv_xyz",
  "action": "get_user_context",
  "input": {},
  "timestamp": 1710000000123
}

The user.id is always taken from the verified session — never from model arguments. Unverified sessions do not receive connector tools.

Signature

Header format: X-Supportal-Signature: t=<unix seconds>,v1=<hex> where v1 = HMAC-SHA256(connector_secret, "{t}.{rawBody}"). Reject if |now - t| > 300 seconds.

Verify — Node.js

javascript
import crypto from "node:crypto";

export function verifySupportalSignature(rawBody, header, secret) {
  const m = /^t=(\d+),v1=([a-f0-9]+)$/i.exec(header ?? "");
  if (!m) return false;
  const t = Number(m[1]);
  if (Math.abs(Date.now() / 1000 - t) > 300) return false;
  const expected = crypto
    .createHmac("sha256", secret)
    .update(`${t}.${rawBody}`)
    .digest("hex");
  return expected === m[2];
}

Verify — Python

python
import hmac, hashlib, re, time

def verify_supportal_signature(raw_body: bytes, header: str, secret: str) -> bool:
    m = re.match(r"t=(\d+),v1=([a-f0-9]+)", header or "", re.I)
    if not m:
        return False
    t = int(m.group(1))
    if abs(time.time() - t) > 300:
        return False
    msg = f"{t}.".encode() + raw_body
    expected = hmac.new(secret.encode(), msg, hashlib.sha256).hexdigest()
    return expected == m.group(2)

Verify — Go

go
func VerifySupportalSignature(rawBody []byte, header, secret string) bool {
    re := regexp.MustCompile(`^t=(\d+),v1=([a-f0-9]+)$`)
    m := re.FindStringSubmatch(header)
    if m == nil {
        return false
    }
    t, _ := strconv.ParseInt(m[1], 10, 64)
    if math.Abs(float64(time.Now().Unix()-t)) > 300 {
        return false
    }
    mac := hmac.New(sha256.New, []byte(secret))
    mac.Write([]byte(fmt.Sprintf("%d.", t)))
    mac.Write(rawBody)
    expected := hex.EncodeToString(mac.Sum(nil))
    return expected == m[2]
}

User context

Default path /user-context (configurable). Return arbitrary JSON about the user — plan, orders, entitlements. Fetched lazily as the get_user_context tool.

Custom actions

Define actions in the dashboard with name, description, JSON Schema input, path, and requires_confirmation. The model may call enabled actions; confirmed actions follow the same awaiting_user flow as refunds.

Refunds

POST {base}/refunds with input { orderId, amountMinor, currency, reason, dryRun }. Response { status: "approved"|"rejected"|"pending", refundId?, message? }. Always honor dryRun: true for eligibility checks before executing a real refund.

json
{
  "orderId": "ord_99",
  "amountMinor": 1299,
  "currency": "USD",
  "reason": "Damaged item",
  "dryRun": true
}