Skip to main content

Incoming data and outgoing webhooks

Use a private external source to accept text from your system, then subscribe to state transitions. This is not an interface for uploading custom executable handlers. REST uses workspace API keys; MCP uses its separate OAuth connection. All endpoints.

Receive text materials​

Create a source with POST /v1/external-sources, then upload to /v1/external-sources/{id}/items. Use the owner's configuration key for setup and a separate, source-restricted ingest key for the sending system.

ingest_materials body
{
"items": [{
"externalId": "article-42",
"version": "1",
"title": "Release notes",
"text": "The release is available.",
"publishedAt": "2026-10-20T12:00:00Z",
"url": "https://example.com/releases/42",
"metadata": {"category": "release"}
}]
}

externalId, version, title, text and publishedAt are required. Use ISO 8601 with a timezone. URL and JSON metadata are optional. Limits: 50 items per batch, 65,536 text characters per item, 1,000 title characters, 16 KiB metadata and 1 MiB request body. Images use the separate asset upload operation; do not put image binaries in text.

The HTTP response can be accepted while individual items are rejected. Inspect every items[index] result: accepted includes a raw id and jobId; duplicate refers to an already saved version; rejected includes a code. An accepted receipt's job identifier may precede a workspace job row while the durable outbox is dispatched. Read source results and status as well as the job endpoint during that interval.

Deduplication uses workspace + source + external ID + version. A repeated saved version does not rerun processing or charge intake. Changed content under the same version is a conflict; submit a new version for a real update. After a timeout, first repeat the original request and idempotency key. Do not turn a transient transport error into a new content version.

Subscribe to results​

create_webhook body
{
"name": "Our result receiver",
"url": "https://receiver.example.com/signailist",
"events": ["processing.completed", "digest.ready", "publication.sent"],
"includeResult": true
}

This is a placeholder URL, not a working receiver. Replace it with your public HTTPS endpoint. Save the returned signing secret in your secret manager immediately: it is shown once. An idempotent response replay retains the subscription ID but omits its secret. Rotate if you lost the original response.

EventMeaning
processing.completedPreprocessing was saved; subsequent AI readiness must be checked separately
processing.failedA workspace processing/AI job failed; inspect the referenced job
digest.readyDigest entered a ready/review state; not proof of publication
approval.requiredA publication candidate needs approval
publication.sentA publication delivery transitioned to sent
publication.failedA publication delivery failed
publication.unknownProvider outcome is uncertain; do not automatically resend
source.failedA source entered an error state
balance.lowBalance crossed down through USD 1
balance.depletedBalance crossed down through zero
limit.reachedA workspace or connection spending limit blocked a stage

The versioned envelope has schemaVersion, id, type, createdAt, workspaceId, objectId and data. With includeResult: false, data is a short object reference/status; with true, data contains the snapshot captured at the transition. Different events for the same object can represent different transitions. Fetch the latest object through REST/MCP if you need current state rather than the event snapshot.

Verify, persist and acknowledge​

Headers: Signailist-Event-Id, Signailist-Timestamp (Unix seconds), Signailist-Signature. Compute HMAC-SHA256 of the UTF-8 timestamp, a literal dot, then the exact raw request body bytes. The signature is hexadecimal prefixed v1=. Compare in constant time and reject timestamps more than five minutes from your clock; keep your server clock synchronized. Match the header event ID with the parsed envelope ID after signature verification.

Download the Python verifier. Do not verify a JSON serialization of the parsed body: whitespace and key order can change the signature. Rotation overlaps old/new secrets for 24 hours; the header can contain comma-separated signatures. Accept a valid signature from either currently trusted secret during the transition.

Persist a verified event and its deduplication ID durably, then return any 2xx promptly. Queue expensive work in your own system. If you cannot persist the event, return an error so Signailist can retry. Delivery is at least once; duplicates and reordering are expected. Do not perform an unprotected side effect before recording deduplication state.

Retries, billing and recovery​

Signailist performs up to eight attempts over 24 hours with backoff, then marks delivery failed. Use list_webhook_deliveries or GET /v1/webhooks/deliveries for status, attempts and last HTTP result. retry_webhook restarts a failed paid notification; { "ok": true } is acknowledgement of the request, not proof the receiver accepted it. Events retain their ID across retries. History is retained for 30 days.

Actual physical delivery attempts consume the common workspace balance, including failed HTTP attempts. A repeated client request alone does not create another charge. Balance/limit notifications have a limited free lane, maximum three attempts per event; they cannot be restarted as paid-style free retries. See billing.

Only public HTTPS destinations are accepted. Credentials in URLs, private/local addresses and unsafe DNS answers are rejected. Redirects are not followed. A receiver that moves must update its URL/configuration rather than rely on redirecting to another host. Pausing the subscription prevents delivery; it does not remove already saved history.

Outbound webhooks are unrelated to a publication provider's delivery safety. A failed webhook can be retried; a publication.unknown event is a reason to investigate the destination, not permission to send the publication again.