Skip to main content

Overview

Instead of polling the status endpoint, you can provide a callback_url in your interactive notification request. NextKS will POST the results to your URL when the request finishes.

When callbacks fire

A callback is triggered when:
  • onUserAction mode: All users have responded
  • onTimeout mode: The timeout is reached
  • Either mode: The timeout is reached and some users haven’t responded

Signature verification

Every callback request is signed with HMAC-SHA256 so you can verify it came from NextKS. When you send an interactive notification with a callback_url, the API response includes a callback_secret. Store this secret securely — you’ll use it to verify incoming callbacks.

Verification headers

HeaderDescription
X-NextKS-Signaturesha256=<hex> — HMAC-SHA256 of the raw request body, keyed with your callback_secret
X-NextKS-Request-IdThe request_id from the original notification

Verification example

import crypto from 'crypto'

function verifyCallback(body: string, signature: string, secret: string): boolean {
  const expected = 'sha256=' + crypto
    .createHmac('sha256', secret)
    .update(body)
    .digest('hex')
  return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))
}

Callback payload

NextKS sends a POST request with Content-Type: application/json:
{
  "request_id": "req_a1b2c3d4e5f6",
  "external_reference_id": "deploy-v2.4.1",
  "status": "finished",
  "responses": [
    {
      "user_email": "alice@company.com",
      "option_value": "approve",
      "timestamp": "2026-02-11T14:23:01.000Z",
      "expired": false
    },
    {
      "user_email": "bob@company.com",
      "option_value": null,
      "timestamp": null,
      "expired": true
    }
  ]
}

Callback URL requirements

Your callback URL is validated when you submit the notification:
RequirementDetails
ProtocolHTTPS only
DNSMust resolve to a public IP address
ReachabilityMust respond to a HEAD request within 5 seconds
Private IPsBlocked (10.x, 172.16-31.x, 192.168.x, 127.x, 169.254.x)
If validation fails, the notification request is rejected with a 400 error.

Retries

If a callback delivery fails (network error or non-2xx response), NextKS retries automatically with exponential backoff:
AttemptDelay after failureTotal elapsed
1 (initial)immediate0
21 minute~1 min
32 minutes~3 min
44 minutes~7 min
58 minutes~15 min
616 minutes~31 min
  • Up to 6 total attempts (1 initial + 5 retries)
  • Each attempt is logged with the error message
  • Once successful, no further retries are attempted

Handling callbacks

Your endpoint should:
  1. Return a 2xx status code to acknowledge receipt
  2. Process the payload asynchronously if needed — NextKS has a 10-second timeout
  3. Be idempotent — in rare cases, a callback may be delivered more than once
import crypto from 'crypto'
import express from 'express'

const app = express()

// Store secrets when creating notifications: { requestId: callbackSecret }
const callbackSecrets: Record<string, string> = {}

app.post('/hooks/nextks', express.raw({ type: 'application/json' }), (req, res) => {
  const signature = req.headers['x-nextks-signature'] as string ?? ''
  const requestId = req.headers['x-nextks-request-id'] as string ?? ''
  const secret = callbackSecrets[requestId]

  // Verify signature
  if (secret) {
    const expected = 'sha256=' + crypto
      .createHmac('sha256', secret)
      .update(req.body)
      .digest('hex')
    if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
      return res.status(403).send('Invalid signature')
    }
  }

  const data = JSON.parse(req.body.toString())
  const approved = data.responses
    .filter((r: any) => !r.expired)
    .every((r: any) => r.option_value === 'approve')

  if (approved) {
    triggerDeployment(data.external_reference_id)
  }

  res.sendStatus(200)
})