Skip to Content
Authentication

Authentication

Only the partner backend calls /api/external/*. Requests are authenticated with canonical auth v2: an HMAC-SHA256 signature over a canonical string built from the request. The scheme is fixed by server-side partner configuration and cannot be selected per request.

Required headers

HeaderValue
X-Partner-IdYour SWEEP_PARTNER_ID
X-TimestampUnix epoch milliseconds, decimal; max clock skew ±5 minutes
X-Sweep-Auth-VersionLiteral 2, mandatory on every request
X-NonceUnique per request, 16–128 chars of A-Za-z0-9._~-; never reused, including on retries
X-SignatureLowercase hex HMAC-SHA256 of the signing string
Idempotency-KeyPOST only, 1–255 chars
Content-Typeapplication/json (POST only)

Signing string

Sign this exact LF-delimited string, no trailing newline:

SWEEP-EXTERNAL-REQUEST auth-version:2 api-version:1 partner-id:<partner id> timestamp:<epoch milliseconds> nonce:<unique request nonce> method:<uppercase HTTP method> path:<normalized absolute pathname> idempotency-key:<key, or empty for GET> body-sha256:<SHA-256 hex of the exact raw request bytes>
import crypto from 'node:crypto'; const sha256 = (v) => crypto.createHash('sha256').update(v).digest('hex'); const hmac = (secret, v) => crypto.createHmac('sha256', secret).update(v).digest('hex'); export function signSweepRequest(secret, r) { const signingString = [ 'SWEEP-EXTERNAL-REQUEST', 'auth-version:2', 'api-version:1', `partner-id:${r.partnerId}`, `timestamp:${r.timestamp}`, `nonce:${r.nonce}`, `method:${r.method.toUpperCase()}`, `path:${r.path}`, `idempotency-key:${r.idempotencyKey || ''}`, `body-sha256:${sha256(r.rawBody || '')}`, ].join('\n'); return hmac(secret, signingString); }

Rules:

  • Serialize a POST body exactly once. Hash, sign and transmit the same bytes.
  • path is the full deployed pathname (/api/external/...) with no query string, fragment, dot-segments or encoded separators.
  • GET requests have no body, no Idempotency-Key header, sign an empty idempotency value and use the SHA-256 of zero bytes.
  • Duplicated protected headers, non-identity Content-Encoding, a GET body or an Idempotency-Key on GET are rejected.

The complete client is in Reference code.

Idempotency

Every mutating endpoint requires an Idempotency-Key. Persist the key and the exact body before transmission. On timeout or an ambiguous response, retry the same bytes with the same key. Use a fresh key only for a new operation or genuinely additive evidence. Never reuse a key for a different body or resource.

GET /ping

Auth and connectivity probe. Use it to validate credentials and the signature implementation.

Request:

GET /api/external/ping x-partner-id: partner-example x-timestamp: 1893456000000 x-sweep-auth-version: 2 x-nonce: req-00000000-0000-4000-8000-000000000001 x-signature: <lowercase HMAC-SHA256 hex>

Response 200:

{ "apiVersion": "1", "ok": true, "partnerId": "partner-example" }
import { SweepExternalClient } from 'sweep-external-sdk/server'; const client = new SweepExternalClient({ sweepApiUrl: process.env.SWEEP_API_URL, partnerId: process.env.SWEEP_PARTNER_ID, secret: process.env.SWEEP_PARTNER_SECRET, }); const result = await client.request('GET', '/api/external/ping'); // result.response => { apiVersion: '1', ok: true, partnerId: '...' }

Troubleshooting

ResponseCause and fix
401 external_auth_signature_invalidSigned bytes differ from transmitted bytes. Serialize once; sign the full deployed pathname; a single trailing slash is stripped before verification
401 external_auth_request_invalidURL carries a query string or another rejected path form
401 external_auth_timestamp_invalid / external_auth_timestamp_expiredX-Timestamp is not decimal epoch milliseconds, or clock skew exceeds five minutes
401 external_auth_nonce_invalidNonce outside A-Za-z0-9._~- or not 16–128 chars
409 external_auth_nonce_conflictNonce reused; generate a fresh nonce for every request, including idempotent retries
401 external_auth_headers_requiredX-Sweep-Auth-Version or X-Nonce missing
401 external_auth_version_mismatchHeader disagrees with server-side partner configuration; the version cannot be chosen per request. If correctly signed v2 requests fail with this, the partner record needs a Sweep-side fix — contact support, do not rewrite the signer
Last updated on