Authentication
Qefro authenticates platform → application calls with a shared signing secret and HMAC-SHA256.
Customer/user authentication inside tools (OTP, CRM login) is separate — see ctx.requireCustomer / challenges in tools.md.
Connection secret
External SDK Connection
- Created with Org Portal Signing Secret or generated when omitted.
- Stored encrypted on ACS (
encrypted_signing_secret). - Must match
Qefro({ signingSecret })/QEFRO_SIGNING_SECRETin your process. - Returned once on create/update when plaintext is available (
SdkConnectionWithSecret). List APIs exposehas_secretonly.
Managed app
- Runtime receives a signing secret via environment / platform injection (
QEFRO_SIGNING_SECRETis the conventional name used by examples). - Connector-manager may use service default
QEFRO_SIGNING_SECRETor per-invokesigning_secret.
Signature format
Verified by @qefro-ai/backend verifySignature and produced by ACS SdkWebhookClient::sign / connector-manager sign_v1:
payload = "v1:" + timestamp + ":" + raw_request_body
signature = "v1=" + hex( HMAC_SHA256(signing_secret, payload) )
Example (illustrative — not a real secret):
POST /qefro HTTP/1.1
Content-Type: application/json
X-Qefro-Signature: v1=0123abcd… # hex digest
X-Qefro-Timestamp: 1710000000
X-Qefro-Protocol: 1
{"protocol_version":"1","request_id":"req_1","type":"ping"}
Header names are matched case-insensitively by HTTP stacks; the SDK reads lowercase x-qefro-signature / x-qefro-timestamp. ACS also sets X-Qefro-Signature / X-Qefro-Timestamp.
Replay protection
- Timestamp must be finite Unix seconds.
- Default max skew: 300 seconds (
maxTimestampSkewSeconds). - Outside window → signature verification fails →
invalid_signature.
Verification (SDK)
// Inside listen / handleRaw — you normally do not call this yourself:
app.verifySignature(signatureHeader, timestampHeader, rawBody);
Failed verification on HTTP listener:
{ "type": "error", "code": "invalid_signature", "message": "Invalid Qefro signature" }
HTTP status 401.
Secret rotation
ACS update API: set signing_secret on PATCH /api/v1/org/sdk-connections/{id}. Empty/null generation behavior follows UpdateSdkConnectionRequest (rotate / generate new). Redeploy your app with the new value before/when cutting over.
What this is not
HMAC authenticates Qefro platform → your /qefro. It does not replace:
- Customer OTP / login challenges
- Org Portal / admin user JWTs
- Internal service bearer tokens used for storage-service (
Authorization: Bearer …onctx.storagecalls)