docs / webhooks

Supply chain webhooks

Team-tier accounts can register HTTPS endpoints that receive a signed POST when Attestd confirms a supply chain compromise on a monitored package. Use webhooks to trigger internal alerts, block deploys, or notify on-call without polling the API.

registration

Register an endpoint

Webhooks require the Team plan. Register endpoints in the portal webhooks page or via the API. Each subscription needs an HTTPS URL and a signing secret (16 to 64 characters). Up to 5 active subscriptions per account.

bash
curl -X POST "https://api.attestd.io/v1/webhooks" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://hooks.example.com/attestd",
    "secret": "your-signing-secret-min-16-chars",
    "events": ["supply_chain.compromise"]
  }'

See the API reference for list and delete endpoints.

payload

Event payload

Attestd POSTs JSON with Content-Type: application/json. The body matches what the delivery worker enqueues on a real compromise:

json
{
  "event": "supply_chain.compromise",
  "package": "langchain",
  "version": "0.1.20",
  "ecosystem": "pypi",
  "source": "osv",
  "malware_type": "malicious_publish",
  "description": "Malicious code in langchain (PyPI)",
  "advisory_url": "https://osv.dev/vulnerability/MAL-2024-1234",
  "osv_id": "MAL-2024-1234"
}

Field semantics:

  • event is always supply_chain.compromise.
  • package, version, and ecosystem identify the compromised release.
  • source is the detection source (for example osv).
  • malware_type, description, advisory_url, and osv_id come from the incident record and may be null.

The portal "send test" button queues a synthetic delivery with source: "test":

json
{
  "event": "supply_chain.compromise",
  "package": "langchain",
  "version": "0.1.20",
  "ecosystem": "pypi",
  "source": "test",
  "malware_type": null,
  "description": "This is a test delivery from Attestd.",
  "advisory_url": null,
  "osv_id": null
}
security

Signature verification

Each delivery includes X-Attestd-Signature: sha256=…. The value is HMAC-SHA256 over the raw request body (compact JSON, sorted keys) using your signing secret. Verify before acting on the payload.

Python

python
import hashlib
import hmac

def verify_attestd_webhook(body: bytes, signature_header: str, secret: str) -> bool:
    expected = "sha256=" + hmac.new(
        secret.encode(), body, hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, signature_header)

Node.js

javascript
import crypto from "node:crypto";

function verifyAttestdWebhook(body: Buffer, signatureHeader: string, secret: string): boolean {
  const expected =
    "sha256=" +
    crypto.createHmac("sha256", secret).update(body).digest("hex");
  return crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(signatureHeader)
  );
}
delivery

Retries and constraints

  • Deliveries run on a 60-second worker cycle.
  • Failed deliveries retry up to 5 attempts while status remains pending.
  • Endpoint URLs must use HTTPS. Credentials in the URL are rejected.
  • Private, loopback, and reserved IP ranges are blocked at registration and send time.
  • Redirect responses (3xx) are not followed.
  • User-Agent is Attestd-Webhooks/1.0.
related