Skip to content

Languages without an SDK

Integrating from a language other than Node or Go (PHP, Python, Java, C#, Ruby …) is straightforward: the API is plain HTTPS + JSON, authentication is HTTP Basic, and a webhook signature is one HMAC. With this page you write a small client and a webhook verifier in your own language — 30 to 60 minutes.

  • You need: an API key of the app — a client_id (gk_test_… / gk_live_…, or the «default key»'s mapp_test_… / mapp_…) and its secret (gks_… / gma_cs_…), the webhook endpoint's secret (whsec_…), an HTTP client, and a crypto library with HMAC-SHA256.

1. The HTTP API in brief

Item Rule
Base URL https://geregeapp.mn/api/v1/miniapp
Authentication Authorization: Basic base64(client_id:secret) — the only way in. Every key has scopes: a key missing a route's scope gets 403 insufficient_scope with required_scope (the «default key» has every scope of its environment). Test and live keys are separate — only the key's environment is visible (livemode)
A mini app's person token Authorization: Bearer gma_at_… (creating an app intent, /me) — launch and tokens
Body Content-Type: application/json, UTF-8. Sending an unknown field answers 400 invalid_request — send only documented fields
Money Whole tögrög (3500 = 3,500₮), currency is MNT
Time RFC 3339, UTC (2026-10-22T09:00:00Z)
Idempotency-Key Required to create an intent and to refund: 1–128 printable ASCII, no spaces. On a network error, 429 or 5xx retry with the same key
Errors The module's {"error": "<Mongolian text>", "code": "<code>", …} or OAuth's {"error": "<code>", "error_description": "…"} — branch on the code (Error codes)
Pagination, limits API conventions, Limits
curl -s -u "$GEREGE_CLIENT_ID:$GEREGE_CLIENT_SECRET" \
  -H "Content-Type: application/json" -H "Idempotency-Key: order_8812" \
  -d '{"amount": 3500, "description": "Ус 0.5л", "reference": "order_8812", "presentment": "qr"}' \
  https://geregeapp.mn/api/v1/miniapp/payment-intents

When writing the client:

  • Reading an error: if there is a code field, that is the code and error is a message you may show a person; if there is no code but error looks like [a-z_]+ (OAuth), error is the code; if neither ({"error": "not found"}, 429, 500), name the code after the HTTP status (http_404 and so on) — exactly what the SDKs do.
  • Log the X-Request-Id response header — Support will want it.
  • A 20–30 second timeout; retry with exponential backoff and the same Idempotency-Key.
  • The client secret lives on servers only — never in a browser, a mobile app or a terminal (Security).

2. Verifying webhooks

The algorithm, the headers, the details and ready-made functions for Node, Go, Python and PHP are on Verifying signatures. Java and C# below — the same logic (|now − t| ≤ 300, any v1, constant time, lower-case hex):

import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.HexFormat;
import java.util.List;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

public final class GeregeWebhook {
    public static boolean verify(byte[] rawBody, String header, List<String> secrets, long now) throws Exception {
        long t = -1;
        List<byte[]> sigs = new ArrayList<>();
        for (String part : (header == null ? "" : header).split(",")) {
            String p = part.trim();
            int i = p.indexOf('=');
            String key = i < 0 ? p : p.substring(0, i);
            String value = i < 0 ? "" : p.substring(i + 1);
            if (key.equals("t") && value.matches("[0-9]{1,18}")) t = Long.parseLong(value);
            else if (key.equals("v1")) sigs.add(value.getBytes(StandardCharsets.US_ASCII));
        }
        if (t < 0 || sigs.isEmpty() || Math.abs(now - t) > 300) return false;
        boolean ok = false;
        for (String secret : secrets) {
            Mac mac = Mac.getInstance("HmacSHA256");
            mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
            mac.update((t + ".").getBytes(StandardCharsets.UTF_8));
            byte[] want = HexFormat.of().formatHex(mac.doFinal(rawBody)).getBytes(StandardCharsets.US_ASCII);
            for (byte[] sig : sigs) ok = MessageDigest.isEqual(sig, want) | ok;
        }
        return ok;
    }
}
using System.Linq;
using System.Security.Cryptography;
using System.Text;

public static class GeregeWebhook
{
    public static bool Verify(byte[] rawBody, string? header, IEnumerable<string> secrets, long now, long tolerance = 300)
    {
        long t = -1;
        var sigs = new List<byte[]>();
        foreach (var part in (header ?? "").Split(','))
        {
            var p = part.Trim();
            var i = p.IndexOf('=');
            var key = i < 0 ? p : p[..i];
            var value = i < 0 ? "" : p[(i + 1)..];
            if (key == "t" && value.Length is > 0 and <= 18 && value.All(char.IsAsciiDigit)) t = long.Parse(value);
            else if (key == "v1") sigs.Add(Encoding.ASCII.GetBytes(value));
        }
        if (t < 0 || sigs.Count == 0 || Math.Abs(now - t) > tolerance) return false;
        var ok = false;
        foreach (var secret in secrets)
        {
            using var mac = new HMACSHA256(Encoding.UTF8.GetBytes(secret));
            var message = Encoding.UTF8.GetBytes($"{t}.").Concat(rawBody).ToArray();
            var want = Encoding.ASCII.GetBytes(Convert.ToHexString(mac.ComputeHash(message)).ToLowerInvariant());
            foreach (var sig in sigs) ok = CryptographicOperations.FixedTimeEquals(sig, want) | ok;
        }
        return ok;
    }
}

Take the body as bytes before your framework parses it (Spring: @RequestBody byte[]; ASP.NET Core: Request.EnableBuffering() and copy Request.Body into a MemoryStream).

3. Testing your code

  • The test vectors generated from the platform's own code (vectors.json: 20 signing and verification cases) ship with the SDKs. Until the SDKs are published, get the file from Support; load it in your unit tests and check that each webhook.verification[] case matches its valid (example).
  • Without the vectors: the secret, t and body of the «worked example» on Verifying signatures must give aab41441…c9821.
  • With a real delivery: the console's «Send test» (webhook.test) — through a tunnel to your machine (Local webhooks).