Hoppa till innehåll
  • Det finns inga förslag eftersom sökfältet är tomt.

Core 5.7.4 / 6.0.0 Admin API Migration Guide for Partners

This article explains the changes partners and API integrators must make for Core 5.7.4 and Core 6.0.0. Core 5.7.4 introduced security improvements and an API restructuring, and deprecated several legacy methods. Core 6.0.0 will be updated 11th of November 2026, removes those legacy methods permanently

Partner action is required. Legacy authentication methods and deprecated APIs are removed in Core 6.0.0. Partners that still use them must migrate. Calls that use removed methods are rejected, typically with 403 Forbidden or 404 Not Found.

All partners = changes that affect everyone using Dstny APIs.
Whitelabel partners only = changes that affect Admin API access only.


1. User Ticket API: new secure ticket creation [All partners]

The method for creating Bearer tokens (User API tickets) was updated in Core 5.7.4.

What changed

Bearer tickets used to be created by MD5-hashing the user's password. That method is deprecated and replaced by a new method that:

  • uses the username and password directly
  • supports 2FA (two-factor authentication) when it is enabled for the user
  • works for all User API calls

Password-based ticket creation via MD5 is removed in Core 6.0.0. If your integration creates tickets this way, you must update it.

How to create tickets going forward

  • Manually in the Core portal at bc.dstny.se (Sköndal environment) or bc2.dstny.se (Gothenburg environment)
  • Via the updated User Ticket API. End users create a ticket with their username and password (and a 2FA code if 2FA is enabled for the user). The new method creates tickets that can access any Core User API method. See the User API documentation.

Admin management of user tickets

A separate Admin API method creates User (Bearer) tickets and replaces the previous method. See the User Ticket API for details. Organization managers can no longer create User (Bearer) tickets. Use Service Accounts for this purpose. Service Account authorization to create User API tickets is supported from Core 5.7.5.

How to check if you are affected

Core 5.7.4 and later add audit log fields that identify deprecated usage. Look for ticketType: "password_based". It means your integration still uses the deprecated method.


2. MD5 signing replaced by HMAC-SHA256 (SNT-HMACv1) [Whitelabel partners only]

The authorization signature used in older releases for the Admin and Management APIs is replaced by a more secure method, inspired by the signing procedures in OAuth 1.0 (see RFC 5849). The new authorization scheme is called SNT-HMACv1. It applies to Admin and System tickets, which are only accepted through the Management Node.

The new method uses a longer secret and a stronger signing algorithm (HMAC-SHA256). Protection against replay attacks is improved by combining a unique random nonce with a timestamp.

Area Legacy method New method
Algorithm MD5 HMAC-SHA256
Secret format Hexadecimal string Base64 encoded string (RFC 4648)
Replay protection Limited Timestamp + nonce

Ticket compatibility

Existing API tickets can still be used. They support both the legacy signing method and the new HMAC-SHA256 method. However:

  • API consumers must use the new secret format (Base64) when existing tickets are used. HMAC-SHA256 uses a binary secret, so the Base64 format is needed to decode the value from a string. You can fetch the secret from the Management Node portal or with the Management Ticket API.
  • It is recommended to revoke existing tickets and create new ones, so that all tickets use the longer secret introduced in Core 5.7.4.

Admin and System tickets are created in the Core management portal, or with the Management Ticket API (method Create ticket).

Support for the legacy signature is removed in Core 6.0. Replace existing integrations with the HMAC-SHA256 method as soon as the Management Node is upgraded to Core 5.7.4, to ensure compatibility with later releases. After the removal, Admin API calls with MD5 signatures stop working.

How to check if you are affected

Look for ticketType: "noauth_token" in the audit logs. It means MD5 signing is still in use.

2.1 Request requirements

  • Admin API calls go to the Management Node: https://{managementnode}/api/admin/...
  • Send Accept: application/json. When you send a JSON body, also send Content-Type: application/json. For PATCH, use Content-Type: application/json-patch+json.
  • Non-ASCII characters in URLs must be UTF-8 percent-encoded. Boolean values must be lowercase (true, false).

2.2 Authorization header

Authorization: SNT-HMACv1 token="{TICKET}",signature_method="HMAC-SHA256",signature="{SIGNATURE}",timestamp="{UNIX_SECONDS}",nonce="{RANDOM_STRING}"
Field Rule
token The Admin or System API ticket.
signature_method The only supported method is HMAC-SHA256.
signature The calculated signature: Base64 encoded, then RFC 5849 percent-encoded.
timestamp Seconds since 1 January 1970 00:00:00 UTC. Requests with a timestamp too far from server time are rejected, so keep your clock synchronised (NTP).
nonce A random string generated by the client for each request. It must be unique across all requests that share the same timestamp and ticket. It may only contain characters that need no encoding: A–Z a–z 0–9 - . _ ~.

2.3 Building the string to sign

Join eight parts with & (ASCII 38):

{Token}&{METHOD}&{scheme}&{authority}&{path}&{query}&{timestamp}&{nonce}
Part Rule Example
Token As issued 1.VDpjZDRjMDA0NjE1MDM5ZmYz
Method HTTP method in upper case GET
Scheme Lower case https
Authority Lower case, percent-encoded. The port is included only when it is not the default (443 for https, 80 for http). mn.example.org%3A9443
Path Percent-encoded, including the slashes %2Fapi%2Fadmin%2Fuser%2Fsn1.com
Query The URL query parameters, combined with any application/x-www-form-urlencoded form parameters. Each key and value is percent-encoded separately, the resulting key=value pairs are sorted lexicographically, and they are joined with &. excludeType=exchange&excludeType=number&query=alice%20with%20space
Timestamp Same value as in the header 1761050015
Nonce Same value as in the header si073w

No query parameters: the query part is an empty string, so two & follow each other: ...&{path}&&{timestamp}&{nonce}.

Percent-encoding (RFC 5849 section 3.6): every character except A–Z a–z 0–9 - . _ ~ is encoded as %XX with upper-case hex digits. A space becomes %20, never +.

Encode exactly once. Sign the decoded values and encode them once. If your HTTP library gives you an already-encoded path or query (for example alice%20with%20space), decode it first. Otherwise the % is encoded again and the signature does not match.

2.4 Calculating the signature

  1. Base64-decode the ticket secret to raw bytes. These bytes are the HMAC key. Do not use the Base64 text itself as the key.
  2. Compute HMAC-SHA256(key, string-to-sign), with the string encoded as UTF-8.
  3. Base64-encode the result.
  4. Percent-encode the Base64 string per RFC 5849 (+ becomes %2B, / becomes %2F, = becomes %3D) and place it in signature="...".

2.5 Test vector

Use this to check your implementation before calling the API.

Input Value
Request GET https://mn.example.org:9443/api/admin/user/sn1.com?query=alice%20with%20space&excludeType=number&excludeType=exchange
Token 1.VDpjZDRjMDA0NjE1MDM5ZmYz
Secret (Base64) caxYXlH6daU=
Timestamp 1761050015
Nonce si073w

String to sign:

1.VDpjZDRjMDA0NjE1MDM5ZmYz&GET&https&mn.example.org%3A9443&%2Fapi%2Fadmin%2Fuser%2Fsn1.com&excludeType=exchange&excludeType=number&query=alice%20with%20space&1761050015&si073w

Expected signature (after step 4): KMM431%2BJ%2FdPlh9DdS1%2Bq1FgEYmYQ%2BKsNzkz572QjTwk%3D

Expected request:

GET api/

The path in this vector is a legacy path, used only to give a fixed example. For real calls, use the current endpoints described in section 3.

2.6 Code examples

In the examples, timestamp and nonce are generated for each request. To reproduce the test vector, pass the fixed timestamp and nonce from section 2.5.

Python

import base64, hashlib, hmac, secrets, time
from urllib.parse import urlparse, parse_qsl, quote, unquote

def enc(s: str) -> str:
    # RFC 5849 section 3.6: only ALPHA / DIGIT / - . _ ~ stay unencoded; space -> %20
    return quote(s, safe='')

def build_base(token, method, url, timestamp, nonce, form_params=None):
    u = urlparse(url)
    scheme = u.scheme.lower()
    host = u.hostname.lower()
    default = {'https': 443, 'http': 80}[scheme]
    authority = host if (u.port is None or u.port == default) else f"{host}:{u.port}"
    # Query parameters and (if any) application/x-www-form-urlencoded body parameters are signed together.
    params = parse_qsl(u.query, keep_blank_values=True) + list((form_params or {}).items())
    query = '&'.join(sorted(f"{enc(k)}={enc(v)}" for k, v in params))
    return '&'.join([token, method.upper(), scheme, enc(authority), enc(unquote(u.path)),
                     query, str(timestamp), nonce])

def auth_header(token, secret_b64, method, url, form_params=None, timestamp=None, nonce=None):
    timestamp = timestamp or int(time.time())
    nonce = nonce or secrets.token_hex(6)
    base = build_base(token, method, url, timestamp, nonce, form_params)
    key = base64.b64decode(secret_b64)  # the secret is Base64: decode it to raw bytes
    sig = base64.b64encode(hmac.new(key, base.encode('utf-8'), hashlib.sha256).digest()).decode()
    return (f'SNT-HMACv1 token="{token}",signature_method="HMAC-SHA256",'
            f'signature="{enc(sig)}",timestamp="{timestamp}",nonce="{nonce}"')

 

Java (11+)

import java.net.URI;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.*;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

public class SntHmac {
    // RFC 5849 section 3.6: only ALPHA / DIGIT / - . _ ~ stay unencoded; space -> %20
    static String enc(String s) {
        StringBuilder sb = new StringBuilder();
        for (byte b : s.getBytes(StandardCharsets.UTF_8)) {
            char c = (char) (b & 0xFF);
            if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || "-._~".indexOf(c) >= 0)
                sb.append(c);
            else sb.append(String.format("%%%02X", b & 0xFF));
        }
        return sb.toString();
    }

    static String dec(String s) { return URLDecoder.decode(s.replace("+", "%2B"), StandardCharsets.UTF_8); }

    /** formParams: application/x-www-form-urlencoded body parameters as {key, value} (decoded), or null. */
    static String header(String token, String secretB64, String method, String url,
                         List<String[]> formParams, long timestamp, String nonce) throws Exception {
        URI u = URI.create(url);
        String scheme = u.getScheme().toLowerCase();
        int defaultPort = scheme.equals("https") ? 443 : 80;
        String authority = u.getHost().toLowerCase()
            + ((u.getPort() == -1 || u.getPort() == defaultPort) ? "" : ":" + u.getPort());

        // Query parameters and form parameters are signed together, sorted as "key=value" strings.
        List<String> params = new ArrayList<>();
        if (u.getRawQuery() != null && !u.getRawQuery().isEmpty())
            for (String kv : u.getRawQuery().split("&")) {
                int i = kv.indexOf('=');
                String k = i < 0 ? kv : kv.substring(0, i), v = i < 0 ? "" : kv.substring(i + 1);
                params.add(enc(dec(k)) + "=" + enc(dec(v)));
            }
        if (formParams != null) for (String[] p : formParams) params.add(enc(p[0]) + "=" + enc(p[1]));
        Collections.sort(params);

        String base = String.join("&", token, method.toUpperCase(), scheme, enc(authority),
            enc(u.getPath()), String.join("&", params), String.valueOf(timestamp), nonce);

        Mac mac = Mac.getInstance("HmacSHA256");
        mac.init(new SecretKeySpec(Base64.getDecoder().decode(secretB64), "HmacSHA256")); // secret: Base64 -> bytes
        String signature = Base64.getEncoder().encodeToString(mac.doFinal(base.getBytes(StandardCharsets.UTF_8)));

        return "SNT-HMACv1 token=\"" + token + "\",signature_method=\"HMAC-SHA256\",signature=\"" + enc(signature)
            + "\",timestamp=\"" + timestamp + "\",nonce=\"" + nonce + "\"";
    }
}

C# (.NET 5+)

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

public static class SntHmacSigner
{
    public static string BuildAuthorizationHeader(string url, string token, string secretBase64, HttpMethod method)
    {
        string timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString();
        string nonce = Convert.ToHexString(RandomNumberGenerator.GetBytes(6)).ToLowerInvariant();
        string baseString = BuildBaseString(url, token, method.Method, timestamp, nonce);

        byte[] key = Convert.FromBase64String(secretBase64); // the secret is Base64: decode it to raw bytes
        using var hmac = new HMACSHA256(key);
        string signature = Encode(Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes(baseString))));

        return $"SNT-HMACv1 token=\"{token}\",signature_method=\"HMAC-SHA256\"," +
               $"signature=\"{signature}\",timestamp=\"{timestamp}\",nonce=\"{nonce}\"";
    }

    private static string BuildBaseString(string url, string token, string method, string timestamp, string nonce)
    {
        var uri = new Uri(url);
        string host = uri.Host.ToLowerInvariant();
        string authority = uri.IsDefaultPort ? host : $"{host}:{uri.Port}";   // default ports are omitted
        string path = Encode(Uri.UnescapeDataString(uri.AbsolutePath));
        string query = EncodeAndSortQuery(uri.Query);                          // empty query -> empty string
        return $"{token}&{method.ToUpperInvariant()}&{uri.Scheme.ToLowerInvariant()}&{Encode(authority)}&{path}&{query}&{timestamp}&{nonce}";
    }

    private static string EncodeAndSortQuery(string rawQuery)
    {
        var pairs = new List<string>();
        foreach (var part in rawQuery.TrimStart('?').Split('&', StringSplitOptions.RemoveEmptyEntries))
        {
            int i = part.IndexOf('=');
            string key = Uri.UnescapeDataString(i >= 0 ? part[..i] : part);
            string value = i >= 0 ? Uri.UnescapeDataString(part[(i + 1)..]) : "";
            pairs.Add($"{Encode(key)}={Encode(value)}");
        }
        pairs.Sort(StringComparer.Ordinal);
        return string.Join("&", pairs);
    }

    // RFC 5849 section 3.6
    private static string Encode(string s) =>
        Uri.EscapeDataString(s).Replace("+", "%20").Replace("*", "%2A").Replace("%7E", "~");
}

Node.js

const crypto = require('crypto');
const enc = s => encodeURIComponent(s).replace(/[!'()*]/g, c => '%' + c.charCodeAt(0).toString(16).toUpperCase());

function buildBase(token, method, url, timestamp, nonce, formParams = []) {
  const u = new URL(url);
  const scheme = u.protocol.slice(0, -1).toLowerCase();
  const host = u.hostname.toLowerCase();
  const authority = u.port ? `${host}:${u.port}` : host; // URL already drops default ports
  // Query parameters and (if any) application/x-www-form-urlencoded body parameters are signed together.
  const params = [...u.searchParams, ...formParams];
  const query = params.map(([k, v]) => `${enc(k)}=${enc(v)}`).sort().join('&');
  return [token, method.toUpperCase(), scheme, enc(authority), enc(decodeURIComponent(u.pathname)),
          query, timestamp, nonce].join('&');
}

function authHeader(token, secretB64, method, url, formParams = [],
                    timestamp = Math.floor(Date.now() / 1000),
                    nonce = crypto.randomBytes(6).toString('hex')) {
  const base = buildBase(token, method, url, timestamp, nonce, formParams);
  const key = Buffer.from(secretB64, 'base64'); // the secret is Base64: decode it to raw bytes
  const sig = crypto.createHmac('sha256', key).update(base, 'utf8').digest('base64');
  return `SNT-HMACv1 token="${token}",signature_method="HMAC-SHA256",signature="${enc(sig)}",timestamp="${timestamp}",nonce="${nonce}"`;
}

2.7 Troubleshooting 403 Forbidden

  1. Node: Admin and System calls must go to the Management Node. Other nodes return 401/403.
  2. Legacy signing: check the audit log for ticketType: "noauth_token". It means MD5 signing is still in use.
  3. Secret handling: the HMAC key is the Base64-decoded secret, not the Base64 text and not a hex string.
  4. Double encoding: sign decoded values and encode once. %2520 in the string to sign means a value was encoded twice.
  5. Encoding rules: spaces as %20, upper-case hex digits, slashes in the path as %2F, and the colon before a non-default port as %3A.
  6. Port: leave out port 443 (https) and port 80 (http). Include any other port.
  7. Query and form parameters: encode names and values separately, sort the key=value pairs, and sign exactly the parameters you send. For application/x-www-form-urlencoded requests, include the form parameters.
  8. Signature encoding: percent-encode the Base64 signature (+, /, =).
  9. Clock and nonce: keep the clock NTP-synchronised and use a new nonce for each request.
  10. Endpoint: a call to a removed endpoint fails regardless of the signature. See section 3.

3. User Admin API replaced by three new APIs [Whitelabel partners only]

The legacy User Admin API is split into three APIs:

  • User Administration API for user management
  • Bulk User Administration API for bulk operations
  • User Secret API for passwords, PINs and device secrets

The legacy User Admin API is deprecated since Core 5.7.4 and removed in Core 6.0.0.

3.1 User Administration API

User management moves to the new User Administration API.

Legacy (removed) Replacement
GET /api/admin/user/{domain} (list and search users) GET /api/admin/useradmin/list/v1/{domain}?query=...
GET /api/admin/user/{domain}/{user} GET /api/admin/useradmin/v1/{domain}/{user}
PATCH /api/admin/user/{domain}/{user} PATCH /api/admin/useradmin/v1/{domain}/{user} (JSON Patch)

The two legacy user listing methods are merged into a single List users method. It returns, for each user, name, userId and primaryNumber.

Changes to the user object:

  • field1–field6 are replaced by dirFields.
  • resetPassword is no longer supported.
  • callRecordingAgentDesktop is removed.
  • thirdPartyPhone is replaced by thirdPartyPhoneType.
  • password and pincode are no longer part of user objects. They moved to the User Secret API.

3.2 Bulk User Administration API

  • Bulk user operations moved to the new Bulk User Administration API (/api/admin/bulkuser/...).
  • The ResetPassword column is no longer included in CSV exports.
  • Only the full-view CSV format is supported.

3.3 User Secret API

Passwords, PINs and third-party device secrets are managed through the User Secret API:

  • Secrets can only be set, never read back.
  • Only the clear-text password format is supported. The legacy MD5 password format is removed.
  • When a new user is created, the system generates a password automatically.

4. How to identify deprecated API usage

Core 5.7.4 and later add fields to the audit log so you can see whether your integration still uses deprecated methods.

Field Value Meaning
ticketType noauth_token Deprecated MD5 signing in use
ticketType password_based Deprecated password-based ticket creation in use
deprecated {"forRemoval":true,"since":"5.7.4"} A deprecated API endpoint was called

The audit log also records the API that was called: "category":"api" together with the "action" field identifies the API, and "type" identifies the HTTP method (read for GET, create for POST, update for PUT and PATCH, delete for DELETE). Example entry (abbreviated):

json

{ "time":"2025-11-04T09:16:26.226Z", "category":"api", "auth":"ticket",
  "ticket":"(md5)2ebf7cc82ad16041ab5c80b4c49967b9", "ticketType":"noauth_token",
  "type":"create", "action":"/api/tickets/example.com/jeff",
  "deprecated":{"forRemoval":true,"since":"5.7.4"}, "note":"create API ticket" }

The API documentation in Core 5.7.4 and later also shows in which Core version each method was deprecated. Several other deprecated APIs besides the User Admin API are removed in Core 6.0.0. See APIs deprecated and scheduled for removal in Core 6.0.0. No deprecated methods are planned for removal in any Core 5.7.x release.

Make sure no deprecated calls remain in your integration before your Core environment is upgraded to 6.0.0.


5. Timeline

Version Status
Core 5.7.4 Released 24 February 2026. Introduced the new APIs and deprecated the legacy methods.
Core 6.0.0 11 November 2026. Removes the deprecated methods.

Complete the migration steps in this article before Core environment is upgraded to 6.0.0. 


Partner checklist before Core 6.0.0

  • [All partners] Update ticket creation to the new User Ticket API method. Remove any MD5 password-based ticket creation.
  • [Whitelabel] Replace MD5 signing with HMAC-SHA256 (SNT-HMACv1) in all Admin API requests, and verify your implementation against the test vector in section 2.5.
  • [Whitelabel] Use the Base64 secret (from the Management Node portal or the Management Ticket API) and ideally recreate tickets to get the longer secret.
  • [Whitelabel] Migrate from the legacy User Admin API to the User Administration, Bulk User Administration and User Secret APIs.
  • [All partners] Review the audit logs for noauth_token, password_based and deprecated flags. Confirm that no deprecated calls remain.

Full API documentation: User API | Admin API | Core Admin API & User API overview

Questions about your specific integration? Contact your Dstny partner contact or partnersupport@dstny.se.

120 31