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
private_key.jsonfrom Generate Key Pairs.- A
Bn-Api-Keyissued by Bahn during onboarding.
API details
| Method | POST |
| URL | https://bn.cakedigibank.tech/api/playground/concat-strings |
| Description | Concatenates two strings and returns the result. |
| Request | string1 (string) — first string; string2 (string) — second string |
| Response | result (string) — the two strings concatenated |
Prepare request content
Original request content
The raw JSON body for this endpoint:
{
"string1": "Hello",
"string2": "World"
}JWS payload
Base64url-encode the original request body to produce the JWS payload:
eyJzdHJpbmcxIjoiSGVsbG8iLCJzdHJpbmcyIjoiV29ybGQifQJWS header
| Field | Value | Purpose |
|---|---|---|
alg | ES256 | Signature algorithm. |
typ | jose+json | Token type. |
kid | your signing key ID | Tells Bahn which registered public key to verify with. |
url | request path | Binds the token to this endpoint — prevents replay to a different API. |
iat | Unix timestamp (seconds) | Allows Bahn to reject stale tokens. |
Construct the protected header JSON with the required fields:
{
"alg": "ES256",
"iat": 1773720077,
"kid": "00b06c00-3278-4f09-bf9b-6825cf889131",
"typ": "jose+json",
"url": "/api/playground/concat-strings"
}Then base64url-encode it:
eyJhbGciOiJFUzI1NiIsImlhdCI6MTc3MzcyMDA3Nywia2lkIjoiMDBiMDZjMDAtMzI3OC00ZjA5LWJmOWItNjgyNWNmODg5MTMxIiwidHlwIjoiam9zZStqc29uIiwidXJsIjoiL2FwaS9wbGF5Z3JvdW5kL2NvbmNhdC1zdHJpbmdzIn0JWS signature
Sign the string base64url(header) + "." + base64url(payload) with your private ES256 signing key, then base64url-encode the result:
bH6Q--yv7BQUZsK1gP-n1kBqMWe2ZlNfbzyRjl2-Qh3VGuAEwSuc_Zvp6kc3LIANSkp5jr-SU0tUUajia4axvQCreate 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:
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):
{
"payload": "eyJzdHJpbmcxIjoiSGVsbG8iLCJzdHJpbmcyIjoiV29ybGQifQ",
"signatures": [
{
"protected": "eyJhbGciOiJFUzI1NiIsImlhdCI6MTc3MzcyMDA3Nywia2lkIjoiMDBiMDZjMDAtMzI3OC00ZjA5LWJmOWItNjgyNWNmODg5MTMxIiwidHlwIjoiam9zZStqc29uIiwidXJsIjoiL2FwaS9wbGF5Z3JvdW5kL2NvbmNhdC1zdHJpbmdzIn0",
"signature": "bH6Q--yv7BQUZsK1gP-n1kBqMWe2ZlNfbzyRjl2-Qh3VGuAEwSuc_Zvp6kc3LIANSkp5jr-SU0tUUajia4axvQ"
}
]
}When using
application/jose+jsonwith multiple signatures, Bahn uses the first entry for verification.
Prepare headers
HTTP headers
| Header | Value | Notes |
|---|---|---|
Content-Type | application/jose or application/jose+json | Format of the signed body. |
Bn-Api-Key | your API key | Identifies your merchant account. |
Bn-Jose-Method | jws | Tells Bahn the body is JWS-signed. |
Bn-Request-Id | fresh UUID per attempt | Must be a new value on every attempt, including retries. |
Bn-Idempotency-Key | stable key per operation | Must stay the same across retries of the same operation. |
Send request
Compact serialization:
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:
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:
{
"result": "HelloWorld"
}Code example
1. Set up
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/uuid2. Write the code
Create main.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:
go run main.goExpected output:
jws: eyJhbGciOiJFUzI1NiIsImlhdCI6...
status : 200
response: {"result":"HelloWorld"}Next steps
- Browse the API Reference to start building real payment flows.