Skip to content

Security Layers

Every partner request passes through several layers of security. The sequence below shows how keys, tokens, and request signing fit together.

  1. The client provides Pipevest with an ED25519 public key.
  2. Pipevest provides the client with a client ID and client secret.
  1. The client exchanges the client ID and client secret for a short-lived access token (with a TTL).
  2. The client makes an HTTPS request that carries:
    • a Content-Digest hash to maintain request-body integrity, and
    • a Signature over the request’s message components to maintain their integrity.
  3. Pipevest parses the HTTPS request and:
    • rejects any request that does not originate from the credential’s allowlisted IP address(es) (403),
    • rejects any request whose credential has no public key registered (401), and
    • rejects any request that fails Content-Digest or Signature verification (401).

Signing is mandatory, not a go-live switch — an unsigned request to a partner endpoint is rejected today. The IP allowlist is enforced whenever your credential has one configured; leave it empty and any source IP is accepted.

Signing applies to every endpoint that authenticates you as a partner — that is, every endpoint you call with an Authorization: Bearer <access token> plus X-Client-Id:

Endpoint Signed
POST /v1/customers Yes
GET /v1/customers Yes
GET /v1/customers/{id} Yes
PATCH /v1/customers/{id} Yes
GET /v1/payment/status/{orderId} Yes
POST /v1/ussd/session Yes
POST /v1/ussd/process Yes
GET /v1/oauth/me Yes
POST /v1/oauth/revoke Yes

Three endpoints are deliberately not signed, because you cannot yet hold a partner access token when you call them:

  • POST /v1/oauth/token — the token exchange itself. It authenticates with your client ID and client secret. Signature headers sent on it are ignored.
  • GET /v1/webhooks/key — public. Public keys are not secret, so no authentication is required.
  • POST /v1/webhooks/callback — authenticated with the shared secret agreed during onboarding.

The callback secret may be presented two ways, and the header is preferred — a query parameter leaks into access logs and referrer headers along the way.

Channel Form
Header (preferred) x-callback-secret: <secret>
Query parameter ?token=<secret> on the callback url

The rules between them:

  • A present, non-empty x-callback-secret header is authoritative. A wrong value in it is a 401 even when the url also carries a correct ?token= — the weaker channel cannot override the stronger one, so a caller sending the wrong secret finds out rather than being quietly upgraded.
  • An empty or whitespace-only header carries no credential, so it is treated as absent and the ?token= value is used. A client that always emits the header — blank when it has nothing configured for it — still authenticates on a valid token.
  • Surrounding whitespace decides only whether a secret was presented; the value itself is compared verbatim and in constant time.

Neither channel is a fallback for the other being wrong: present the secret once, through the header, unless your platform can only ride it on the url.