Using Newforma Konekt's Webhooks

This page will help you get started with Newforma Konekt's Webhooks.

Use webhooks to receive event notifications from Newforma Konekt in near real time. Webhooks can be registered at the hub scope or at the project scope and are delivered with at-least-once semantics.

Concepts

TermMeaning
Hub-scoped webhookReceives matching events across an entire hub.
Project-scoped webhookReceives matching events only for one project.
TopicStable customer-facing token used for subscription matching and sent in X-Newforma-Event.
Event body typeSource producer event identifier. For compatibility, this may differ from the topic token. Route on X-Newforma-Event if you need the stable public topic.
eventIdStable identifier for the event across retries.
deliveryIdPer-attempt identifier. Use this value when contacting support about a failed delivery attempt.

Delivery Model

  • Deliveries are at-least-once.
  • Retries use exponential backoff and can span roughly 24 hours.
  • A webhook can be auto-paused after repeated failures.
  • The per-hub limit is 100 non-archived webhooks.

Listener Requirements

  • Use https:// on port 443.
  • Return a 2xx response within 15 seconds.
  • Do not redirect requests.
  • Expect request bodies up to roughly 256 KB.
  • Keep your response body at 8 KB or less.
  • Your endpoint must resolve to a public IP. Private, loopback, link-local, metadata, and similar addresses are blocked.

Authentication And Signing

Every delivery can include:

  • Authorization: Bearer <authToken> if the webhook was configured with an auth token.
  • X-Newforma-Timestamp with the Unix timestamp used for signing.
  • X-Newforma-Signature with sha256=<hex> where the signed payload is ${timestamp}.${rawBody}.

Reject any request outside a 5 minute replay window and compare signatures in constant time.

Request Headers

HeaderMeaning
Content-TypeAlways application/json
AuthorizationOptional bearer token configured on the webhook
X-Newforma-EventStable public topic token such as rfi.created
X-Newforma-Event-IdStable event identifier
X-Newforma-Delivery-IdPer-attempt delivery identifier
X-Newforma-Delivery-AttemptAttempt number starting at 1
X-Newforma-TimestampUnix timestamp used for signing
X-Newforma-Signaturesha256=<hex> signature over ${timestamp}.${rawBody}
User-AgentNewformaKonekt-Webhooks/1.0

Example Delivery

Headers:

X-Newforma-Event: rfi.created
X-Newforma-Event-Id: 9f13d80f-8e49-4d05-8f69-faf8c53eb52f
X-Newforma-Delivery-Id: 8f06a1f4-7d03-4580-949d-c2499d4e06a0
X-Newforma-Delivery-Attempt: 1
X-Newforma-Timestamp: 1780920000
X-Newforma-Signature: sha256=<hex>

Body:

{
    "eventId": "9f13d80f-8e49-4d05-8f69-faf8c53eb52f",
    "type": "RFI_CREATED",
    "hubId": "hub-001",
    "projectId": "42",
    "resourceId": "5001",
    "occurredAt": "2026-06-08T18:20:00.000Z"
}

Topics

Fetch the exact catalog for a hub with GET /hubs/{hubId}/webhooks/events. The current public categories are:

RFI topics

rfi.created, rfi.updated, rfi.deleted, rfi.logged, rfi.received, rfi.forwarded, rfi.reviewSubmitted, rfi.officialResponse, rfi.sent, rfi.closed, rfi.reopened

Submittal topics

submittal.created, submittal.updated, submittal.deleted, submittal.logged, submittal.received, submittal.forwarded, submittal.reviewSubmitted, submittal.officialResponse, submittal.sent, submittal.closed, submittal.reopened

Wildcard tokens:

  • * subscribes to every available topic in scope.
  • rfi.* subscribes to all RFI topics.
  • submittal.* subscribes to all submittal topics.

Management API

Every management route is authenticated with an OAuth 2.0 bearer token and authorized by your role in the target hub or project. The sections below explain how to obtain a token, which scopes it must carry, and who is allowed to manage webhooks.

Authentication methods

Newforma Konekt accepts three kinds of caller credentials. All three resolve to a Konekt user identity; they differ in how the token is obtained and how long it lives. Whatever method you choose, the token must carry the scopes listed in Required scopes below.

Hub owner master token

A hub owner can generate a long-lived master token for the hub. It authenticates as the hub owner — who is implicitly a hub administrator — so it satisfies the authorization checks for every hub-scoped and project-scoped route in that hub. Use it for unattended, server-to-server integrations where no interactive sign-in is possible. Because it is long-lived and grants hub-owner access, store it as a secret and rotate it if it leaks.

User token

A short-lived access token issued for an individual Konekt user. Actions are performed as that user and authorized by that user's hub or project role, so the user must be a hub or project administrator (see Authorization below). Access tokens expire after roughly two hours. User tokens are convenient for manual testing but are not suitable on their own for unattended integrations.

Client ID and secret (OAuth 2.0)

The recommended way to build a production integration. Register your application with the Newforma Konekt team to receive a client ID and secret and to whitelist your redirect URI, then run the PKCE-enhanced authorization code flow to obtain a user access token (and a refresh token when you request offline_access). The token acts as the user who signed in, so that user must be a hub or project administrator.

The full step-by-step flow — code verifier and challenge, /connect/authorize, and /connect/token — is documented in Using OAuth 2.0. Key production endpoints:

PurposeEndpoint
Authorization serverhttps://auth.bimtrackapp.co
Authorizehttps://auth.bimtrackapp.co/connect/authorize
Tokenhttps://auth.bimtrackapp.co/connect/token

Access tokens live for roughly two hours; use the refresh token to renew without re-prompting the user.

Required scopes

Request the following scopes (space-delimited in the scope parameter) when you acquire the token. A token that is missing any of them is rejected even when the user holds the right role.

ScopeWhy it is required
PIMTrack_ApiThe webhook management endpoints are served by the PIM API. Without this scope every management call is rejected at the gateway.
BIMTrack_ApiAuthorization resolves your hub and project role through the Konekt core API. The service forwards your token to that API, so the token must be accepted there.
SubscriptionService_Api_ReadCreate, reactivate, and topic-change calls verify the hub's subscription entitlement through the Subscription Service. Without it those calls fail (see below).

Also include the standard OpenID Connect scopes that the OAuth flow requires: openid (always required) and offline_access (only when you need a refresh token). A minimal scope value for full webhook management is:

openid PIMTrack_Api BIMTrack_Api SubscriptionService_Api_Read

SubscriptionService_Api_Read is the read-only subscription scope; the broader SubscriptionService_Api also satisfies the check. These scopes are enabled per registered client, so when you register an OAuth application ask the Newforma Konekt team to enable PIMTrack_Api and SubscriptionService_Api_Read for it.

Authorization

Regardless of how the token was obtained, the resolved user must hold the right role:

  • Hub-scoped routes (/hubs/{hubId}/webhooks...): a hub administrator or the hub owner.
  • Project-scoped routes (/hubs/{hubId}/projects/{projectId}/webhooks...): a hub administrator, the hub owner, or an administrator of that project.

A token that authenticates but lacks the required role receives 403. A token that is missing SubscriptionService_Api_Read receives 403 with subscription_insufficient on the subscription-gated operations (create, reactivate, and topic changes), even for a hub administrator.

RouteMethodsDescription
/hubs/{hubId}/webhooks/eventsGETList available topics and wildcard tokens.
/hubs/{hubId}/webhooksGET, POSTList or create hub-scoped webhooks.
/hubs/{hubId}/webhooks/{webhookId}GET, PATCH, DELETERead, update, or delete one hub-scoped webhook.
/hubs/{hubId}/webhooks/{webhookId}/pingPOSTSend a synchronous test delivery to a hub-scoped webhook.
/hubs/{hubId}/projects/{projectId}/webhooksGET, POSTList or create project-scoped webhooks.
/hubs/{hubId}/projects/{projectId}/webhooks/{webhookId}GET, PATCH, DELETERead, update, or delete one project-scoped webhook.
/hubs/{hubId}/projects/{projectId}/webhooks/{webhookId}/pingPOSTSend a synchronous test delivery to a project-scoped webhook.

Create request

{
    "url": "https://example.com/webhooks/newforma",
    "topics": ["rfi.*"],
    "authToken": "optional-token",
    "expiresAt": "2026-12-31T23:59:59.000Z"
}

Create response

{
    "webhook": {
        "id": "507f1f77bcf86cd799439011",
        "scope": "hub",
        "url": "https://example.com/webhooks/newforma",
        "topics": ["rfi.*"],
        "status": "active",
        "consecutiveFailedDeliveries": 0,
        "createdBy": "[email protected]",
        "createdAt": "2026-06-08T18:20:00.000Z",
        "updatedAt": "2026-06-08T18:20:00.000Z",
        "updatedBy": "[email protected]"
    },
    "signingSecret": "whsec_..."
}

Update request

{
    "status": "paused",
    "rotateSigningSecret": true,
    "expiresAt": null
}

Ping response

{
    "success": true,
    "statusCode": 200,
    "durationMs": 42
}

HMAC Verification Recipes

Node.js

import { createHmac, timingSafeEqual } from "node:crypto";

function verify({ secret, timestamp, signature, rawBody }) {
    const ageSeconds = Math.abs(Math.floor(Date.now() / 1000) - Number(timestamp));
    if (ageSeconds > 300) {
        return false;
    }

    const expected = `sha256=${createHmac("sha256", secret).update(`${timestamp}.${rawBody}`).digest("hex")}`;

    return timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
}

.NET

using System.Security.Cryptography;
using System.Text;

static bool Verify(string secret, string timestamp, string signature, string rawBody)
{
    var ageSeconds = Math.Abs(DateTimeOffset.UtcNow.ToUnixTimeSeconds() - long.Parse(timestamp));
    if (ageSeconds > 300)
    {
        return false;
    }

    using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret));
    var payload = Encoding.UTF8.GetBytes($"{timestamp}.{rawBody}");
    var expected = $"sha256={Convert.ToHexString(hmac.ComputeHash(payload)).ToLowerInvariant()}";

    return CryptographicOperations.FixedTimeEquals(
        Encoding.UTF8.GetBytes(expected),
        Encoding.UTF8.GetBytes(signature)
    );
}

Python

import hashlib
import hmac
import time

def verify(secret: str, timestamp: str, signature: str, raw_body: str) -> bool:
    if abs(int(time.time()) - int(timestamp)) > 300:
        return False

    signed_payload = f"{timestamp}.{raw_body}".encode("utf-8")
    expected = "sha256=" + hmac.new(secret.encode("utf-8"), signed_payload, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature)

For other languages, compute sha256= + the hex-encoded HMAC-SHA256 of {timestamp}.{rawBody} with your signing secret, compare it to X-Newforma-Signature using a constant-time comparison, and reject the request if timestamp is more than 300 seconds old.

Retry Behavior

  • Attempt 1 uses the delivery queue.
  • Attempts 2 and 3 are in-band SQS retries.
  • Attempts 4 through 10 are scheduled at approximately 10m, 30m, 1h, 2h, 6h, 12h, and 24h with jitter.
  • After the final failure, the webhook is auto-paused with statusReason = delivery_failures_exhausted.

Troubleshooting

SymptomLikely cause
success = false from .../ping with statusCode = nullDNS, TLS, timeout, or SSRF guard rejection prevented the request.
Delivery retries but never succeedsEndpoint is returning non-2xx, timing out, or redirecting.
403 when creating or reactivatingThe hub subscription does not include the functionality required by one or more requested topic categories.
No webhook arrives for an Azure producerThe platform-azure-notifications requiredSubscriptions.ts entry, or the azureIngress/{producer}.ts mapping or azureIngress/bundles.ts registration, is missing.

When opening a support ticket, include the deliveryId, the approximate UTC time, and the webhook id.


What’s Next

Did this page help you?