Prove a Drupal site by its challenge, or have a code emailed
Signed with the site key: signature is the Ed25519 signature, in
base64url, over the key-sorted canonical JSON of the body without
signature.
For a reachable site, Decionis fetches
https://<site>/.well-known/decionis-challenge over HTTPS with no
redirects, a five-second budget and a 16 KB cap. The site answers a JSON
object with protocol (decionis-drupal-connect/1), purpose
(challenge), connect_id, site_url, challenge, and signature:
the site key's signature over the rest, signed the same way. A match
claims the challenge, once, and connects the site (200). A site that
answers without a valid proof is refused (403 DRUPAL_CHALLENGE_FAILED)
and is not offered a code.
For a site Decionis cannot reach, or that gives no answer at all, a
6-digit code is emailed to the billing address instead (202); finish
with connect/code. Until a code sender is configured, that answers 503
DRUPAL_EMAIL_CODE_UNAVAILABLE.
Connecting binds the site to one Decionis organization for good, creating it the first time, and answers that organization's least-privilege key (role MEMBER, scopes decision:read and decision:write) exactly once: only its hash is kept. Proving a connected site again issues a new key and revokes the one it had. The site starts in Shadow.
The challenge is single-use. A second verify of a used challenge answers 409 DRUPAL_CHALLENGE_ALREADY_USED, and a connect that has connected, failed or sent a code answers 409 DRUPAL_CONNECT_NOT_PENDING: start a new one. When Decionis refuses to bind the site, the answer carries that status with UPSTREAM_UNAVAILABLE.
Request Body
application/json
uuidThe site key's Ed25519 signature, base64url without padding, over the key-sorted canonical JSON of the body without signature.
^[A-Za-z0-9_-]{86}$Response Body
application/json
application/json
application/json
application/json
application/json
application/json
application/json
application/json
application/json
application/json
curl -X POST "https://example.com/v1/drupal/connect/verify" \ -H "Content-Type: application/json" \ -d '{ "protocol": "decionis-drupal-connect/1", "purpose": "verify", "connect_id": "a1b4c748-916a-48f3-ac78-f681f542c9c8", "signature": "string" }'{ "status": "connected", "org_id": "a40f5d1f-d889-42e9-94ea-b9b33585fc6b", "api_key": "string", "mode": "shadow", "entitlement": null}Start connecting a Drupal Commerce site POST
The first call of Decionis for Drupal's connect, made by the module with no credential. It registers the site's Ed25519 public key and answers a one-time challenge for the site to serve, signed, at `/.well-known/decionis-challenge` (see `connect/verify`). A public HTTPS site on the default port is proven by that challenge. A site Decionis cannot reach (a private or local host, plain HTTP or a port) is accepted too, and can be proven only by the 6-digit code `connect/verify` emails to `billing_email`. A public host over plain HTTP or on a port is refused. Nothing is ever fetched from a private address. Not idempotent: each call starts a new connect with a new challenge, valid for `expires_in_seconds`; an unfinished connect expires. Requests are limited per address and per site, and a refused one answers 429 with Retry-After.
Finish connecting a Drupal site with the emailed code POST
Signed with the site key, like `connect/verify`. The code lives at most 300 seconds and allows three attempts: a wrong code answers 400 DRUPAL_CODE_INVALID, and once three have failed, 410 DRUPAL_CODE_EXHAUSTED. A correct code is claimed once and connects the site, with the same answer and the same binding as `connect/verify`. An emailed code proves the billing inbox, not the site: it may connect a new site, never take over a connected one (409 DRUPAL_SITE_ALREADY_CONNECTED). It answers 503 DRUPAL_EMAIL_CODE_UNAVAILABLE while codes cannot be checked.

