Skip to content

Make Your First Request ​

This guide walks through every step of building and sending an authenticated request to the Bahn API. We use POST /api/playground/concat-strings as the working example.

Prerequisites


API details ​

MethodPOST
URLhttps://bn.cakedigibank.tech/api/playground/concat-strings
DescriptionConcatenates two strings and returns the result.
Requeststring1 (string) — first string; string2 (string) — second string
Responseresult (string) — the two strings concatenated

Prepare request content ​

Original request content ​

The raw JSON body for this endpoint:

json
{
  "string1": "Hello",
  "string2": "World"
}

JWS payload ​

Base64url-encode the original request body to produce the JWS payload:

eyJzdHJpbmcxIjoiSGVsbG8iLCJzdHJpbmcyIjoiV29ybGQifQ

JWS header ​

FieldValuePurpose
algES256Signature algorithm.
typjose+jsonToken type.
kidyour signing key IDTells Bahn which registered public key to verify with.
urlrequest pathBinds the token to this endpoint — prevents replay to a different API.
iatUnix timestamp (seconds)Allows Bahn to reject stale tokens.

Construct the protected header JSON with the required fields:

json
{
  "alg": "ES256",
  "iat": 1773720077,
  "kid": "00b06c00-3278-4f09-bf9b-6825cf889131",
  "typ": "jose+json",
  "url": "/api/playground/concat-strings"
}

Then base64url-encode it:

eyJhbGciOiJFUzI1NiIsImlhdCI6MTc3MzcyMDA3Nywia2lkIjoiMDBiMDZjMDAtMzI3OC00ZjA5LWJmOWItNjgyNWNmODg5MTMxIiwidHlwIjoiam9zZStqc29uIiwidXJsIjoiL2FwaS9wbGF5Z3JvdW5kL2NvbmNhdC1zdHJpbmdzIn0

JWS signature ​

Sign the string base64url(header) + "." + base64url(payload) with your private ES256 signing key, then base64url-encode the result:

bH6Q--yv7BQUZsK1gP-n1kBqMWe2ZlNfbzyRjl2-Qh3VGuAEwSuc_Zvp6kc3LIANSkp5jr-SU0tUUajia4axvQ

Create signed body ​

Combine the three parts to form the final signed body. Choose the format that suits your integration.

Option A — Compact serialization (application/jose)

Concatenate header, payload, and signature with . separators:

text
eyJhbGciOiJFUzI1NiIsImlhdCI6MTc3MzcyMDA3Nywia2lkIjoiMDBiMDZjMDAtMzI3OC00ZjA5LWJmOWItNjgyNWNmODg5MTMxIiwidHlwIjoiam9zZStqc29uIiwidXJsIjoiL2FwaS9wbGF5Z3JvdW5kL2NvbmNhdC1zdHJpbmdzIn0
.
eyJzdHJpbmcxIjoiSGVsbG8iLCJzdHJpbmcyIjoiV29ybGQifQ
.
bH6Q--yv7BQUZsK1gP-n1kBqMWe2ZlNfbzyRjl2-Qh3VGuAEwSuc_Zvp6kc3LIANSkp5jr-SU0tUUajia4axvQ

<base64url(header)>.<base64url(payload)>.<base64url(signature)>

Option B — JSON serialization (application/jose+json)

Wrap the parts in a JSON object (RFC 7515 §7.2):

json
{
  "payload": "eyJzdHJpbmcxIjoiSGVsbG8iLCJzdHJpbmcyIjoiV29ybGQifQ",
  "signatures": [
    {
      "protected": "eyJhbGciOiJFUzI1NiIsImlhdCI6MTc3MzcyMDA3Nywia2lkIjoiMDBiMDZjMDAtMzI3OC00ZjA5LWJmOWItNjgyNWNmODg5MTMxIiwidHlwIjoiam9zZStqc29uIiwidXJsIjoiL2FwaS9wbGF5Z3JvdW5kL2NvbmNhdC1zdHJpbmdzIn0",
      "signature": "bH6Q--yv7BQUZsK1gP-n1kBqMWe2ZlNfbzyRjl2-Qh3VGuAEwSuc_Zvp6kc3LIANSkp5jr-SU0tUUajia4axvQ"
    }
  ]
}

When using application/jose+json with multiple signatures, Bahn uses the first entry for verification.


Prepare headers ​

HTTP headers ​

HeaderValueNotes
Content-Typeapplication/jose or application/jose+jsonFormat of the signed body.
Bn-Api-Keyyour API keyIdentifies your merchant account.
Bn-Jose-MethodjwsTells Bahn the body is JWS-signed.
Bn-Request-Idfresh UUID per attemptMust be a new value on every attempt, including retries.
Bn-Idempotency-Keystable key per operationMust stay the same across retries of the same operation.

Send request ​

Compact serialization:

bash
curl -X POST 'https://bn.cakedigibank.tech/api/playground/concat-strings' \
  -H 'Content-Type: application/jose' \
  -H 'Bn-Api-Key: api_key-ab318df034' \
  -H 'Bn-Jose-Method: jws' \
  -H 'Bn-Request-Id: 7f3e9c1a-4b2d-4e8f-a1c0-123456789abc' \
  -H 'Bn-Idempotency-Key: demo-concat-001' \
  -d 'eyJhbGciOiJFUzI1NiIsImlhdCI6MTc3MzcyMDA3Nywia2lkIjoiMDBiMDZjMDAtMzI3OC00ZjA5LWJmOWItNjgyNWNmODg5MTMxIiwidHlwIjoiam9zZStqc29uIiwidXJsIjoiL2FwaS9wbGF5Z3JvdW5kL2NvbmNhdC1zdHJpbmdzIn0.eyJzdHJpbmcxIjoiSGVsbG8iLCJzdHJpbmcyIjoiV29ybGQifQ.bH6Q--yv7BQUZsK1gP-n1kBqMWe2ZlNfbzyRjl2-Qh3VGuAEwSuc_Zvp6kc3LIANSkp5jr-SU0tUUajia4axvQ'

JSON serialization:

bash
curl -X POST 'https://bn.cakedigibank.tech/api/playground/concat-strings' \
  -H 'Content-Type: application/jose+json' \
  -H 'Bn-Api-Key: api_key-ab318df034' \
  -H 'Bn-Jose-Method: jws' \
  -H 'Bn-Request-Id: 7f3e9c1a-4b2d-4e8f-a1c0-123456789abc' \
  -H 'Bn-Idempotency-Key: demo-concat-001' \
  -d '{
    "payload": "eyJzdHJpbmcxIjoiSGVsbG8iLCJzdHJpbmcyIjoiV29ybGQifQ",
    "signatures": [
      {
        "protected": "eyJhbGciOiJFUzI1NiIsImlhdCI6MTc3MzcyMDA3Nywia2lkIjoiMDBiMDZjMDAtMzI3OC00ZjA5LWJmOWItNjgyNWNmODg5MTMxIiwidHlwIjoiam9zZStqc29uIiwidXJsIjoiL2FwaS9wbGF5Z3JvdW5kL2NvbmNhdC1zdHJpbmdzIn0",
        "signature": "bH6Q--yv7BQUZsK1gP-n1kBqMWe2ZlNfbzyRjl2-Qh3VGuAEwSuc_Zvp6kc3LIANSkp5jr-SU0tUUajia4axvQ"
      }
    ]
  }'

Expected response:

json
{
  "result": "HelloWorld"
}

Code example ​

1. Set up ​

bash
mkdir bahn-first-request && cd bahn-first-request
go mod init bahn-first-request
go get github.com/go-jose/go-jose/v4
go get github.com/google/uuid

2. Write the code ​

Create main.go:

go
package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"time"

	"github.com/go-jose/go-jose/v4"
	"github.com/google/uuid"
)

const (
	baseURL  = "https://bn.cakedigibank.tech"
	endpoint = "/api/playground/concat-strings"
	signKid  = "00b06c00-3278-4f09-bf9b-6825cf889131"
	apiKey   = "api_key-ab318df034"
)

// loadSignKey reads private_key.json and returns the key matching signKid.
func loadSignKey(path string) (jose.JSONWebKey, error) {
	data, err := os.ReadFile(path)
	if err != nil {
		return jose.JSONWebKey{}, fmt.Errorf("read %s: %w", path, err)
	}
	var keySet jose.JSONWebKeySet
	if err := json.Unmarshal(data, &keySet); err != nil {
		return jose.JSONWebKey{}, fmt.Errorf("unmarshal jwks: %w", err)
	}
	keys := keySet.Key(signKid)
	if len(keys) == 0 {
		return jose.JSONWebKey{}, fmt.Errorf("key %s not found in %s", signKid, path)
	}
	return keys[0], nil
}

func main() {
	// 1. Load the private signing key.
	signKey, err := loadSignKey("private_key.json")
	if err != nil {
		log.Fatalf("load sign key: %v", err)
	}

	// 2. Build the request payload.
	payload, err := json.Marshal(map[string]string{
		"string1": "Hello",
		"string2": "World",
	})
	if err != nil {
		log.Fatalf("marshal payload: %v", err)
	}

	// 3. Sign the payload as a compact JWS.
	signer, err := jose.NewSigner(
		jose.SigningKey{Algorithm: jose.ES256, Key: signKey},
		(&jose.SignerOptions{}).
			WithType("jose+json").
			WithHeader("kid", signKid).
			WithHeader("url", endpoint).
			WithHeader("iat", time.Now().Unix()),
	)
	if err != nil {
		log.Fatalf("new signer: %v", err)
	}

	jws, err := signer.Sign(payload)
	if err != nil {
		log.Fatalf("sign payload: %v", err)
	}

	compact, err := jws.CompactSerialize()
	if err != nil {
		log.Fatalf("serialize jws: %v", err)
	}
	fmt.Printf("jws: %s\n\n", compact)

	// 4. Send the HTTP request.
	req, err := http.NewRequest(http.MethodPost, baseURL+endpoint, bytes.NewBufferString(compact))
	if err != nil {
		log.Fatalf("build request: %v", err)
	}
	req.Header.Set("Content-Type", "application/jose")
	req.Header.Set("Bn-Api-Key", apiKey)
	req.Header.Set("Bn-Jose-Method", "jws")
	req.Header.Set("Bn-Request-Id", uuid.NewString())
	req.Header.Set("Bn-Idempotency-Key", "demo-concat-001")

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		log.Fatalf("do request: %v", err)
	}
	defer resp.Body.Close()

	// 5. Print the response.
	body, _ := io.ReadAll(resp.Body)
	fmt.Printf("status  : %d\nresponse: %s\n", resp.StatusCode, body)
}

3. Run ​

Copy private_key.json to the project directory, then:

bash
go run main.go

Expected output:

jws: eyJhbGciOiJFUzI1NiIsImlhdCI6...

status  : 200
response: {"result":"HelloWorld"}

Next steps ​

Developers