Authentication Code Sample

The Nayax Spark API uses a secure authentication mechanism to protect communication between your integration and the Spark DMZ. This guide shows you the essential code for implementing authentication using cipher and signature generation.

Prerequisites

Before you begin, ensure you have the following from Nayax:

RequirementDescription
Integrator IDYour unique integrator identifier
Token IDThe token identifier for your integration
Secret tokenA secure token string (minimum 32 characters) provided by Nayax
Header hash keyA key used to generate the signature for HTTP headers
Base URLThe Spark API endpoint URL

Generate random number

private string CreateRandom()
{
    Faker faker = new();
    return faker.Random.String2(17, 17, chars: "0123456789");
}

Note: This uses the Bogus library. Install via NuGet: Install-Package Bogus

Create cipher

private string CreateCipher(string sparkTransactionId, string tokenSecretString, string random, bool needTimeStampInRandom = true)
{
    string timeStamp = needTimeStampInRandom ? $"{DateTime.UtcNow:yyMMddHHmm}" : string.Empty;
    string unencryptedCipher = $"{sparkTransactionId}={random}{timeStamp}";
    return CryptoUtils.EncryptAesECB256(unencryptedCipher, tokenSecretString);
}

Format: {sparkTransactionId}={random}{timestamp}

Where:

  • sparkTransactionId: Transaction identifier (use "1234" for testing)
  • random: 17-digit number from CreateRandom()
  • timestamp: UTC time in format yyMMddHHmm (optional)

Create signature

public Dictionary<string, string> CreateSignature<T>(T request, int integratorId) where T : class
{
    string serializedRequest = _jsonClient.Serialize(request, indent: false);
    _integratorDictionary.TryGetValue(integratorId, out Dictionary<string, string> headerHashKeyDictionary);
    if (headerHashKeyDictionary.IsNullOrEmpty() || serializedRequest.IsNullOrEmpty())
    {
        return null;
    }

    headerHashKeyDictionary.TryGetValue("HeaderHashKey", out string integratorValue);
    if (integratorValue.IsNullOrEmpty())
    {
        return null;
    }

    return new Dictionary<string, string>
    {
        { "IntegratorId", integratorId.ToString() }, 
        { "Signature", $"{ByteUtils.CalculateSHA256($"{serializedRequest};{integratorValue}")}" }
    };
}

Format: SHA-256 hash of {serializedRequest};{headerHashKey}

Request model

public class StartAuthenticationRequest3rdParty
{
    public int TokenId { get; set; }
    public string TerminalId { get; set; }
    public int TerminalIdType { get; set; }
    public string Cipher { get; set; }
    public string Random { get; set; }
}

Response model

public class StartAuthenticationResponse3rdParty
{
    [JsonProperty("HashedSparkTransactionId", Required = Required.Always)]
    public string HashedSparkTransactionId { get; set; }

    [JsonProperty("Status", Required = Required.DisallowNull, NullValueHandling = NullValueHandling.Ignore)]
    public Status Status { get; set; }
}

public class Status
{
    public string Verdict { get; set; }
    public int ErrorCode { get; set; }
    public string StatusMessage { get; set; }
}

Trigger transaction

public async Task<TriggerTransactionResponse> TriggerTransaction([FromHeader] int integratorId, [FromBody] TriggerTransactionRequest body)
{
    Dictionary<string, string> signature = _SparkUtils.CreateSignature(body, integratorId);
    RestOptions options = new RestOptions();
    foreach (var headerEntry in signature)
    {
        options.AddHeader(headerEntry.Key, headerEntry.Value);
    }

    ApiResponse<TriggerTransactionResponse> response = await _restClient.SendAsync<TriggerTransactionResponse>(HttpMethod.Post, $"{_baseUrl}/TriggerTransaction", body, options);
  
    return response?.Data ?? new TriggerTransactionResponse
    {
        Status = new Status
        {
            Verdict = "Declined",
            ErrorCode = 29,
            StatusMessage = "Failed to trigger transaction - Not Allowed to Wake Device"
        }
    };
}

Settlement

public async Task<SparkSettlementResponse> Settlement([FromHeader] int integratorId, [FromBody] SparkSettlementRequest body)
{
    Dictionary<string, string> signature = _SparkUtils.CreateSignature(body, integratorId);
    RestOptions options = new RestOptions();
    foreach (var headerEntry in signature)
    {
        options.AddHeader(headerEntry.Key, headerEntry.Value);
    }

    ApiResponse<SparkSettlementResponse> response = await _restClient.SendAsync<SparkSettlementResponse>(HttpMethod.Post, $"{_baseUrl}/Settlement", body, options);
    return response?.Data ?? new SparkSettlementResponse
    {
        Status = new Status
        {
            Verdict = "Declined",
            ErrorCode = 35,
            StatusMessage = "Settlement Failed"
        }
    };
}

Cancel transaction

[HttpPost("CancelTransaction", Name = "CancelTransaction")]
public async Task<SparkCancelTransactionResponse> CancelTransaction([FromHeader] int integratorId, [FromBody] SparkCancelTransactionRequest body)
{
    Dictionary<string, string> signature = _SparkUtils.CreateSignature(body, integratorId);
    RestOptions options = new RestOptions();
    foreach (var headerEntry in signature)
    {
        options.AddHeader(headerEntry.Key, headerEntry.Value);
    }

    ApiResponse<SparkCancelTransactionResponse> response = await _restClient.SendAsync<SparkCancelTransactionResponse>(HttpMethod.Post, $"{_baseUrl}/CancelTransaction", body, options);
    return response?.Data ?? new SparkCancelTransactionResponse
    {
        Status = new Status
        {
            Verdict = "Declined",
            ErrorCode = 31,
            StatusMessage = "Cancellation Failed"
        }
    };
}

Error codes

Error codeDescription
26Cipher is null or integrator ID is invalid
29Not allowed to wake device
31Cancellation failed
35Settlement failed
-9Cipher validation failed