Introduction

Webhooks can be used to get events from Alviere. For example, when a transaction is settled, Alviere can send an event to your webhook consumer with information about the transaction.

Events are grouped into Subscriptions and each Subscription event will be posted into a URL provided by you at the moment of the subscription.

Note: Alviere sends all events asynchronously so that it doesn't block or slow down any system operation or transaction. All events are actions that already took place.

Subscriptions

Alviere provides several different subscriptions to receive different event types:

  • ACCOUNT provides information about the lifecycle of your program accounts.

  • WALLET_TRANSACTION provides information about the lifecycle of wallet transactions.

  • ISSUED_CARD provides information about the lifecycle of all cards issued by Alviere.

  • BENEFICIARY provides information about the lifecycle of all beneficiaries in your program.

  • BANK_PM provides information about the lifecycle of all bank accounts used as payment methods.

  • CARD_PM provides information about the lifecycle of all cards used as payment methods.

  • CHECK provides information about the lifecycle of check deposit transaction.

  • DOSSIER provides information about the lifecycle of all uploaded dossiers.

  • PAYMENT_INSTRUMENT provides information about the lifecycle of all payment instruments.

Payload format

{
  "event_uuid": "082fd7f7-7e9e-4679-bd16-ed9f5a55d827",
  "program_uuid": "04d3ac6e-82d3-4f52-b82f-6cc0320928af",
  "event_date": "2021-06-17T11:02:08.143Z",
  "event_retry": 0,
  "event_type": [SUBSCRIPTION_TYPE],
  "event_version": "2021-11-18.1",
  "entity":{
    ...
  }
}

Authentication

Each subscription requires the following information:

  • Subscription type

  • Target endpoint (needs to be a complete HTTPS URL)

  • Authentication method and credentials

  • Version required for each subscription

Authentication Methods

Option 1: Header-Based Authentication (Default)

When we call your endpoint, we include a header called Alviere-Auth with the security key you provided to us.

Option 2: HMAC Signature Authentication

For enhanced security, you can use HMAC-SHA256 signature verification. Provide your shared secret to your implementation manager, and we'll sign each webhook payload. You can then verify the signature to ensure authenticity.

When using HMAC authentication, webhooks include:

  • Alviere-Signature - HMAC-SHA256 signature of the payload

  • Alviere-Webhook-Id - Unique identifier for the webhook

  • Alviere-Webhook-Timestamp - Unix timestamp when sent

To verify the signature:

  1. Construct the payload: <id>.<timestamp>.<minified-json-body>

  2. Calculate HMAC-SHA256 using your shared secret

  3. Compare with the Alviere-Signature header value

HMAC Verification example:

package main

import (
	"bytes"
	"crypto/hmac"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
)

func calculateHmac(requestBody []byte, alviereWebhookId, alviereWebhookTimestamp, sharedSecret string) (string, error) {
	// remove insignificant spaces (minify)
    bodyBuffer := new(bytes.Buffer)
	if err := json.Compact(bodyBuffer, requestBody); err != nil { return "", err }

	hmacInputBuffer := new(bytes.Buffer)
	hmacInputBuffer.WriteString(alviereWebhookId + "." + alviereWebhookTimestamp + ".")
	hmacInputBuffer.Write(bodyBuffer.Bytes())

	hmacHash := hmac.New(sha256.New, []byte(sharedSecret))
	hmacHash.Write(hmacInputBuffer.Bytes())
	hmacSum := hmacHash.Sum(nil)

	return hex.EncodeToString(hmacSum), nil
}
import hashlib
import hmac
import json

def calculate_hmac(request_body, alviere_webhook_id, alviere_webhook_timestamp, shared_secret):
    # remove insignificant spaces (minify)
    json_object = json.loads(request_body)
    _body = json.dumps(json_object, separators=(',', ':'))

    hmac_input = alviere_webhook_id + "." + alviere_webhook_timestamp + "." + _body
    hmac_object = hmac.new(shared_secret.encode(), hmac_input.encode(), hashlib.sha256)

    return hmac_object.hexdigest()
import { createHmac } from 'node:crypto';

const requestBody = {key: 'value'};
const secret = 'hmac-secret';
const webhookId = 'alviere_webhook_id';
const timestamp = 'alviere_webhook_timestamp';

// remove insignificant spaces (minify)
const body = JSON.stringify(requestBody);

const getHmac = (secret, webhookId, timestamp, body) => {
  const hmac = createHmac('sha256', secret);
  hmac.update([webhookId, timestamp, body].join('.'));
  return hmac.digest('hex');
};

console.log(getHmac(secret, webhookId, timestamp, body));
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.HexFormat;

public class CalculateHmac {

    public static String hmacHex(String request_body, String alviere_webhook_id, String alviere_webhook_timestamp, String shared_secret) {
        String algorithm = "HmacSHA256";
        
        // remove insignificant spaces (minify)
        request_body = minifyJson(request_body);
        
        try {
            SecretKeySpec secret_key = new SecretKeySpec(shared_secret.getBytes("UTF-8"), algorithm);
            Mac sha256_HMAC = Mac.getInstance(algorithm);
            sha256_HMAC.init(secret_key);
            
            String hmac_input = String.join(".", alviere_webhook_id, alviere_webhook_timestamp, request_body);
            
            return HexFormat.of().formatHex(sha256_HMAC.doFinal(hmac_input.getBytes("UTF-8")));
        } catch (Exception e) {
            throw new RuntimeException("Failed to compute HMAC: " + e.getMessage(), e);
        }
    }
    
    public static String minifyJson(String jsonString) {
        StringBuilder result = new StringBuilder();
        boolean inQuotes = false;

        for (int i = 0; i < jsonString.length(); i++) {
            char c = jsonString.charAt(i);
            if (c == '\"') {
                result.append(c);
                inQuotes = !inQuotes;
            } else if (!inQuotes && Character.isWhitespace(c)) {
                // skip
            } else {
                result.append(c);
            }
        }
        return result.toString();
    }
}

Processing

Each time we send a message for the provided URL, we expect a 200 HTTP status code, otherwise we will retry the event applying a polynomial backoff starting at 20ms and going up to 2 minutes after each retry.

NOTE that we will continuously retry sending the message and this will impact any other messages in the queue as we strictly maintain messages in order (FIFO).

How to subscribe

Please contact your implementation manager for more information.