Skip to content

API Guide

Everything you have to build, and the exact shapes involved. All payloads on this page are real responses from a live environment, not illustrations.

Base URL: https://api.omnistrate.cloud/2022-09-01-00/fleet/marketplace

All API calls authenticate with your own Omnistrate credential: Authorization: Bearer $TOKEN.

The One Call You Must Make

Everything else on this page is optional or situational. Confirm is not. Until you call it, no subscription exists, the buyer cannot deploy, and the marketplace is charging them anyway.

Receive the buyer

Step 1. How you are told depends on whether you registered a receiver on the channel. See Where buyers go, and how you are told.

Without a receiver — the browser redirect

Omnistrate redirects the buyer to your callback URL with a credential appended:

GET https://portal.acme.example.com/marketplace/callback?code=hoff_YSYSQA5G489PMVSGGMPV5XB545

The query parameter is named code, and its value is a handoff token — you send it to the redeem endpoint as handoffToken. Nothing about the contract travels in the query string, because anything in a query string can be forged.

With a receiver — the signed webhook

Omnistrate posts contract.discovered to your receiver. It is self-contained: everything needed to provision and confirm is in the body, so no follow-up call is required. It is also the only event that carries handoffToken.

POST https://hooks.acme.example.com/omnistrate
Content-Type: application/json
X-Omnistrate-Event-Id: evt-3c3d521bd975
X-Omnistrate-Event-Type: contract.discovered
X-Omnistrate-Timestamp: 2026-08-25T07:58:45Z
X-Omnistrate-Delivery-Attempt: 1
X-Omnistrate-Signature-256: sha256=24be8a3ec41a191588bb4a28cf5cef833f8fcef9050eada0fbd2d2b140b292b8
{
  "eventId": "evt-3c3d521bd975",
  "eventType": "contract.discovered",
  "occurredAt": "2026-08-25T07:58:45Z",
  "contractVersion": 1,
  "marketplaceContractId": "mkc-i2KCYuleCx",
  "channel": "SANDBOX",
  "externalRef": "sbx-ent-0304eca29fefbc75",
  "buyerRef": "sbx-buyer-7b92e1dd6313",
  "contractStatus": "PENDING",
  "fulfillmentState": "AWAITING_ISV",
  "detectedBy": "EVENT",
  "handoffToken": "hoff_CSW8X7RTYS35CVNZDRT6W4M0TR",
  "handoffExpiresAt": "2026-09-01T07:58:41Z",
  "org": {
    "orgId": "org-zt5jppbqe0",
    "rootUserId": "user-YevTFtlupB",
    "syntheticEmail": "[email protected]"
  },
  "subscriptionRequest": {
    "id": "subr-ljC4UXuw3M",
    "serviceId": "s-ACXVeEr9SN",
    "environmentId": "se-hKwSmAzDp0",
    "productTierId": "pt-PpEw0Ai7JA",
    "status": "PENDING"
  },
  "plan": {
    "planRef": "sbx-plan-standard-hl7r",
    "quantity": 40,
    "currency": "USD",
    "startsAt": "2026-08-25T07:58:41Z"
  },
  "capabilities": {
    "canHoldContract": true,
    "canReportProgress": true,
    "canCancel": true,
    "usageGate": "SOFT"
  }
}

Verifying a delivery

Five headers accompany every request:

Header Value
X-Omnistrate-Event-Id Stable across retries. Deduplicate on it
X-Omnistrate-Event-Type One of the five event types
X-Omnistrate-Timestamp RFC 3339. Reject anything more than five minutes old
X-Omnistrate-Delivery-Attempt 1 on the first try, incrementing
X-Omnistrate-Signature-256 sha256=<hex>, HMAC-SHA256

Sign the timestamp with the body

The signed material is <X-Omnistrate-Timestamp> + . + <raw request body>not the body alone. Binding the timestamp in is what makes the five minute window enforceable: without it, a captured delivery could be replayed forever with a fresh timestamp.

Verify against the raw bytes, before any parsing or re-serialization. A re-encoded body will not match.

const timestamp = req.headers['x-omnistrate-timestamp'];
const signature = req.headers['x-omnistrate-signature-256'];
if (typeof timestamp !== 'string' || typeof signature !== 'string') {
  return res.status(401).end();
}

const signed = `${timestamp}.${rawBody}`;
const expected = 'sha256=' + crypto
  .createHmac('sha256', process.env.OMNISTRATE_WEBHOOK_SECRET)
  .update(signed, 'utf8')
  .digest('hex');

const expectedBuf = Buffer.from(expected, 'utf8');
const signatureBuf = Buffer.from(signature, 'utf8');
if (expectedBuf.length !== signatureBuf.length || !crypto.timingSafeEqual(expectedBuf, signatureBuf)) {
  return res.status(401).end();
}

Receiver requirements

  • HTTPS only. Private, loopback, link-local and cloud metadata addresses are refused, at registration and again at delivery time.
  • Answer within 10 seconds with any 2xx. Do the work asynchronously.
  • Deduplicate on eventId. The same event will arrive more than once. That is normal, not an incident.
  • Discard out-of-order deliveries by dropping any event whose contractVersion is strictly lower than the highest you have applied for that contract. Process an equal version: a redelivery you asked for reuses the version deliberately.

Failed deliveries are retried with backoff for 24 hours, then abandoned and surfaced to you and to Omnistrate support.

Redeem the handoff

Step 2. Exchange the credential for the contract detail.

curl -X POST https://api.omnistrate.cloud/2022-09-01-00/fleet/marketplace/handoff/redeem \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"handoffToken":"hoff_YSYSQA5G489PMVSGGMPV5XB545"}'

The same field carries both arrival paths: the ?code= value from your callback URL, and the handoffToken from a contract.discovered body.

200 · Response

{
  "marketplaceContractId": "mkc-D1uAiFE37D",
  "channel": "SANDBOX",
  "externalRef": "sbx-ent-3045f52c54baa041",
  "buyerRef": "sbx-buyer-36d2a83bddd7",
  "contractStatus": "PENDING",
  "fulfillmentState": "IDENTIFIED",
  "contractVersion": 1,
  "org": {
    "orgId": "org-wg28gu8yc1",
    "rootUserId": "user-76x7Jo9AYT",
    "syntheticEmail": "[email protected]"
  },
  "plan": {
    "planRef": "sbx-plan-standard-hl7r",
    "quantity": 25,
    "currency": "USD",
    "startsAt": "2026-08-25T20:42:28Z"
  },
  "capabilities": {
    "canHoldContract": true,
    "canReportProgress": true,
    "canCancel": true,
    "usageGate": "SOFT"
  },
  "handoffSlaExpiresAt": "2026-08-26T20:42:28Z",
  "handoffTokenExpiresAt": "2026-09-01T20:42:28Z"
}

Behaviour

  • Idempotent and non-consuming. The same token returns the same body until it expires, so a failed provisioning attempt can simply be retried. Making it one-shot would buy nothing against a leaked URL and would turn a buyer refreshing your page into an error.
  • 410 once expired. 404 for a token that is unknown or belongs to another organization — the two are deliberately indistinguishable.

fulfillmentState may read IDENTIFIED, not AWAITING_ISV

Redeem immediately after a redirect and the run may not have reached the handoff stage yet. The contract is still yours to confirm; the state is a snapshot of a workflow that is still moving.

Confirm fulfillment

Step 3, and the one that matters. Call it once the buyer's tenant exists in your product and you are ready to serve them. It moves the subscription request from PENDING to APPROVED, and that approval is what creates the subscription.

curl -X POST https://api.omnistrate.cloud/2022-09-01-00\
/fleet/marketplace/contract/mkc-D1uAiFE37D/fulfillment/confirm \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"externalReference":"acme-tenant-4821"}'
Field Required Meaning
id (path) Yes marketplaceContractId from the event or redeem body
externalReference No Your own id for the tenant you provisioned. Recorded against the contract, so a support conversation can start from your identifier rather than ours

200 · Response

{
  "marketplaceContractId": "mkc-D1uAiFE37D",
  "fulfillmentState": "READY",
  "currentStage": "READY",
  "workflowId": "marketplace-fulfillment-mkc-D1uAiFE37D-v1",
  "subscriptionId": "sub-qNFbtgJFaV",
  "blockedOn": "deployments are allowed, and the channel has not yet opened the usage gate, so nothing is being metered",
  "stages": [ /* every stage, with its status and timings */ ]
}

The response is the confirmation

The call is synchronous. It waits for the approval and returns the subscriptionId it created. There is no second call and no webhook to wait for.

It is idempotent. Confirming twice creates no second subscription: the second call returns the subscription the first produced. Retrying after a timeout is always safe, and it is the intended recovery if you never saw the response.

400 if fulfillment is not waiting for a decision, for example a contract already closed. 404 if the contract is unknown or belongs to another organization.

Where to confirm from the console

An operator can confirm the same contract by hand from FinOps Center → Marketplace Contracts. The action sits on the contract itself, beside Deny, Retry and Cancel.

Confirming a contract from the console

This is the same code path the API uses. It exists for the case where your integration is down and a buyer is waiting, not as the normal route.

Deny fulfillment

Refuse the buyer. No subscription is created and the request is marked denied. Use it when you cannot serve this customer — a conflicting agreement, a sanctions check, an unsupported region.

curl -X POST https://api.omnistrate.cloud/2022-09-01-00\
/fleet/marketplace/contract/mkc-D1uAiFE37D/fulfillment/deny \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"reason":"Buyer domain is covered by an existing agreement"}'

reason is optional and you should send it. It is recorded and shown to operators on both sides, because a refused marketplace purchase is a conversation somebody will have to have.

Reading a contract

Call Returns
GET /contract Every contract for your organization. Add ?includeSimulated=true to include sandbox rehearsals
GET /contract/{id} One contract, as the channel reports it
GET /contract/{id}/fulfillment The fulfillment run: stages, timings, blockedOn, and the workflow id
POST /contract/{id}/fulfillment/retry Re-enter the run and re-emit the handoff

Event types

Five, and the set is fixed. Dispatch on eventType.

Type Meaning You should
contract.discovered A buyer purchased and their organization and pending request exist Provision your tenant, then confirm
entitlement.updated The buyer changed seats, plan or term Re-read plan and adjust their limits
contract.suspended Suspended upstream, usually non-payment Restrict access. Do not delete data
contract.cancelled Cancelled or expired Begin your own offboarding
fulfillment.failed Fulfillment could not proceed, most often because confirm was never called in time Read failureReason. If you can now proceed, confirm

Be idempotent on buyerRef

Not because you will routinely get two arrivals, but because you can: a redelivery you asked for, a retry after your endpoint timed out, or a buyer returning to a bookmarked callback link all produce a second arrival for one buyer. Key the tenant on buyerRef and a repeat becomes a no-op instead of a second tenant.

Integration checklist

  • [ ] Callback URL registered, HTTPS, no query string of your own
  • [ ] Callback handler reads ?code= and calls redeem with it as handoffToken
  • [ ] Receiver registered if you need lifecycle events after the purchase
  • [ ] Signature verified over timestamp + "." + raw body, in constant time
  • [ ] Replay window enforced at five minutes
  • [ ] Deduplicating on eventId
  • [ ] Dropping events with a lower contractVersion
  • [ ] Provisioning keyed on buyerRef, so a repeat is a no-op
  • [ ] Confirm called, with externalReference set to your own tenant id
  • [ ] Confirm retried on timeout rather than treated as failed
  • [ ] Branching on fulfillmentState, never on contractStatus
  • [ ] Whole flow rehearsed end to end in the sandbox