Session revoke webhook
One of the platform → provider webhooks: this direction goes the other way, the platform calls your server, not the reverse. It's how an operator kicking a player reaches a still-open game client — see the inbound webhooks section of the server-integration guide for when and why this fires.
Implement POST /v1/game/session/revoke (that exact path, at the baseUrl your integration contact registered for you) and verify every call with @moosehq/provider-sdk's verifyPlatformSignature before trusting it — this is the platform calling you, so the usual "the SDK signs it for me" story is reversed here.
Request
Signed the same way your own outbound calls are: X-Tenant-ID, X-Timestamp, X-Nonce, X-Signature headers, HMAC-SHA256 over method + "\n" + path + "\n" + timestamp + "\n" + nonce + "\n" + body using your provider secret. X-Tenant-ID carries your own provider tenant ID (the platform is asserting "this call is for you", not naming itself).
Body:
type RevokeSessionRequest = {
sessionToken: string
playerRef: string
gameId: string
reason?: string // e.g. "operator kick" — may be empty
}Response
Return 200 on success. Any other status is logged by the platform but never retried and never changes the kick's outcome — the session is already deleted regardless (see above).
Example (Node/Express)
import { verifyPlatformSignature, type RevokeSessionRequest } from '@moosehq/provider-sdk'
// req.body must be the exact raw string the platform signed, so this
// route needs the raw body, not JSON-parsed middleware.
app.post('/v1/game/session/revoke', express.text({ type: '*/*' }), (req, res) => {
const result = verifyPlatformSignature({
method: req.method,
path: req.path,
headers: req.headers,
body: req.body,
secret: process.env.PLATFORM_SECRET!,
now: new Date(),
expectedTenantId: 'acme-studio', // your own provider tenant ID
})
if (!result.ok) return res.status(401).json({ error: result.reason })
const revoke: RevokeSessionRequest = JSON.parse(req.body)
// End revoke.sessionToken on your side, and — for a browser game —
// tell the running client via your own channel (e.g. the game-client
// SDK's notifySessionRevoked) so it exits immediately instead of
// waiting for its next wallet call to 401.
res.sendStatus(200)
})verifyPlatformSignature
function verifyPlatformSignature(input: VerifyPlatformSignatureInput): VerifyPlatformSignatureResult
type VerifyPlatformSignatureInput = {
method: string
path: string // URL path only — no scheme/host/query
headers: IncomingHeaders // e.g. Node's req.headers — lowercase keys
body: string // the exact raw request body the platform signed
secret: string // your provider secret
now: Date
maxSkewSeconds?: number // defaults to 300 (5 minutes)
expectedTenantId?: string // optional defense-in-depth check
}
type VerifyPlatformSignatureResult =
| { ok: true; tenantId: string }
| {
ok: false
reason:
| 'missing-tenant-id'
| 'tenant-id-mismatch'
| 'missing-timestamp'
| 'timestamp-skew'
| 'missing-nonce'
| 'invalid-signature'
}Unlike the platform's own HMACVerifier, this doesn't consult a previous-secret grace period during rotation — that's a platform-side, per-tenant concern; a provider verifying inbound calls only ever checks against its own single current secret.