SpareSpare Docs
GuidesAPI Reference

Make Your First Payment

Complete a Single Instant Payment in the UAE sandbox using Spare hosted pages.

Integration guide

Goal: Create credentials, register a creditor account, create a Single Instant Payment, and finish authorization on Spare hosted pages.

Estimated time: 25 minutes

Prerequisites

  • Sandbox account with an active pis subscription
  • App ID, API key, and private key (for x-signature)
  • A creditor bank account (portal or API)

Watch a Single Instant Payment in production, powered by Spare:

Before you start

You need an onboarded Spare sandbox account with whitelisted user emails and an active Payment Initiation (pis) subscription. If any of these are missing, contact your Spare sales point of contact or email support@tryspare.com.

Create App ID and API key

Complete Quick Start Setup to create your App ID, API key, and private key, whitelist IPs, and (optionally) configure webhooks.

When you have those credentials stored server-side, return here and continue with the next step.

Onboard creditor account

Register the bank account that receives settled funds:

  1. Sign in to the sandbox dashboard.
  2. Open Payment Initiation β†’ Settings.
  3. On the Bank account page, click Add New Bank account.
  4. Enter account holder details, account type, and bank details.

Sandbox accepts only dummy account identifiers:

FieldSchemeIdentification
Account detailsIBAN10000109010101
Bank detailsBICFI10000109010101

Production registrations use your real IBAN and bank identifiers.

Alternatively, register the bank account over REST or an SDK. See Setup your Bank Account.

Authenticate

Exchange your credentials for a short-lived access token, then include it on every subsequent request.

export SPARE_APP_ID="your-app-id"
export SPARE_API_KEY="your-api-key"
export SPARE_TENANT="UAE"
curl -X POST https://api.sandbox.tryspare.ae/auth/api-keys/sessions \
  -H "x-tenant: UAE" \
  -H "app-id: $SPARE_APP_ID" \
  -H "x-api-key: $SPARE_API_KEY"
const res = await fetch(
  "https://api.sandbox.tryspare.ae/auth/api-keys/sessions",
  {
    method: "POST",
    headers: {
      "x-tenant": "UAE",
      "app-id": process.env.SPARE_APP_ID!,
      "x-api-key": process.env.SPARE_API_KEY!,
    },
  },
);

const { accessToken, expiresIn } = await res.json();
import os
import requests

res = requests.post(
    "https://api.sandbox.tryspare.ae/auth/api-keys/sessions",
    headers={
        "x-tenant": "UAE",
        "app-id": os.environ["SPARE_APP_ID"],
        "x-api-key": os.environ["SPARE_API_KEY"],
    },
)

data = res.json()
access_token = data["accessToken"]
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

HttpClient client = HttpClient.newHttpClient();

HttpRequest request = HttpRequest.newBuilder()
    .uri(URI.create("https://api.sandbox.tryspare.ae/auth/api-keys/sessions"))
    .header("x-tenant", "UAE")
    .header("app-id", System.getenv("SPARE_APP_ID"))
    .header("x-api-key", System.getenv("SPARE_API_KEY"))
    .POST(HttpRequest.BodyPublishers.noBody())
    .build();

HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());

// Parse the JSON response and read the accessToken field
JsonNode data = new ObjectMapper().readTree(response.body());
String accessToken = data.get("accessToken").asText();
using System;
using System.Net.Http;
using System.Text.Json;
using System.Threading.Tasks;

using var client = new HttpClient();

var request = new HttpRequestMessage(
    HttpMethod.Post,
    "https://api.sandbox.tryspare.ae/auth/api-keys/sessions");
request.Headers.Add("x-tenant", "UAE");
request.Headers.Add("app-id", Environment.GetEnvironmentVariable("SPARE_APP_ID"));
request.Headers.Add("x-api-key", Environment.GetEnvironmentVariable("SPARE_API_KEY"));

var response = await client.SendAsync(request);
var body = await response.Content.ReadAsStringAsync();

using var doc = JsonDocument.Parse(body);
var accessToken = doc.RootElement.GetProperty("accessToken").GetString();
package main

import (
	"encoding/json"
	"net/http"
	"os"
)

req, err := http.NewRequest(
	http.MethodPost,
	"https://api.sandbox.tryspare.ae/auth/api-keys/sessions",
	nil,
)
if err != nil {
	panic(err)
}

req.Header.Set("x-tenant", "UAE")
req.Header.Set("app-id", os.Getenv("SPARE_APP_ID"))
req.Header.Set("x-api-key", os.Getenv("SPARE_API_KEY"))

res, err := http.DefaultClient.Do(req)
if err != nil {
	panic(err)
}
defer res.Body.Close()

var data struct {
	AccessToken string `json:"accessToken"`
	ExpiresIn   int    `json:"expiresIn"`
}
json.NewDecoder(res.Body).Decode(&data)

accessToken := data.AccessToken

Tokens expire. Refresh via POST /auth/api-keys/sessions/refresh before they do, or on a 401.

Create a payment request (SIP)

Request signature required

POST /payment-requests requires an x-signature header. See Request signing for the canonical serialization rules and how to build the detached ES256 JWS.

Sign the JSON body itself (canonical serialization), then send that same object as the HTTP body with x-signature. See Request signing for the full walkthrough.

curl -X POST https://api.sandbox.tryspare.ae/payment-requests \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "x-tenant: UAE" \
  -H "Content-Type: application/json" \
  -H "x-signature: $X_SIGNATURE" \
  -d '{
    "type": "SingleInstantPayment",
    "creditorType": "MERCHANT",
    "creditorReference": "Order #9876",
    "merchantReference": "order-9876",
    "purpose": "ACM",
    "creditorAccount": {
      "schemeName": "IBAN",
      "identification": "10000109010101",
      "name": "Acme Trading LLC"
    },
    "instructions": {
      "amount": { "amount": "250.00", "currency": "AED" }
    },
    "successRedirectUrl": "https://yourapp.com/checkout/complete",
    "failureRedirectUrl": "https://yourapp.com/checkout/failed"
  }'
const paymentRequest = {
  type: "SingleInstantPayment",
  creditorType: "MERCHANT",
  creditorReference: "Order #9876",
  merchantReference: "order-9876",
  purpose: "ACM",
  creditorAccount: {
    schemeName: "IBAN",
    identification: "10000109010101",
    name: "Acme Trading LLC",
  },
  instructions: {
    amount: { amount: "250.00", currency: "AED" },
  },
  successRedirectUrl: "https://yourapp.com/checkout/complete",
  failureRedirectUrl: "https://yourapp.com/checkout/failed",
};

// Build xSignature from paymentRequest (see Request signing).

const res = await fetch("https://api.sandbox.tryspare.ae/payment-requests", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${accessToken}`,
    "x-tenant": "UAE",
    "Content-Type": "application/json",
    "x-signature": xSignature,
  },
  body: JSON.stringify(paymentRequest),
});

const { data } = await res.json();
const redirectUrl = data.redirectUrl;
import json
import requests

payment_request = {
    "type": "SingleInstantPayment",
    "creditorType": "MERCHANT",
    "creditorReference": "Order #9876",
    "merchantReference": "order-9876",
    "purpose": "ACM",
    "creditorAccount": {
        "schemeName": "IBAN",
        "identification": "10000109010101",
        "name": "Acme Trading LLC",
    },
    "instructions": {
        "amount": {"amount": "250.00", "currency": "AED"},
    },
    "successRedirectUrl": "https://yourapp.com/checkout/complete",
    "failureRedirectUrl": "https://yourapp.com/checkout/failed",
}

# Build x_signature from payment_request (see Request signing).

res = requests.post(
    "https://api.sandbox.tryspare.ae/payment-requests",
    headers={
        "Authorization": f"Bearer {access_token}",
        "x-tenant": "UAE",
        "Content-Type": "application/json",
        "x-signature": x_signature,
    },
    data=json.dumps(payment_request),
)

data = res.json()["data"]
redirect_url = data["redirectUrl"]
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

String paymentRequestJson = """
    {
      "type": "SingleInstantPayment",
      "creditorType": "MERCHANT",
      "creditorReference": "Order #9876",
      "merchantReference": "order-9876",
      "purpose": "ACM",
      "creditorAccount": {
        "schemeName": "IBAN",
        "identification": "10000109010101",
        "name": "Acme Trading LLC"
      },
      "instructions": {
        "amount": { "amount": "250.00", "currency": "AED" }
      },
      "successRedirectUrl": "https://yourapp.com/checkout/complete",
      "failureRedirectUrl": "https://yourapp.com/checkout/failed"
    }
    """;

// Build xSignature from paymentRequestJson (see Request signing).

HttpRequest request = HttpRequest.newBuilder()
    .uri(URI.create("https://api.sandbox.tryspare.ae/payment-requests"))
    .header("Authorization", "Bearer " + accessToken)
    .header("x-tenant", "UAE")
    .header("Content-Type", "application/json")
    .header("x-signature", xSignature)
    .POST(HttpRequest.BodyPublishers.ofString(paymentRequestJson))
    .build();

HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
// Parse data.redirectUrl from the JSON response.
using System.Net.Http;
using System.Text;
using System.Text.Json;

var paymentRequest = new
{
    type = "SingleInstantPayment",
    creditorType = "MERCHANT",
    creditorReference = "Order #9876",
    merchantReference = "order-9876",
    purpose = "ACM",
    creditorAccount = new
    {
        schemeName = "IBAN",
        identification = "10000109010101",
        name = "Acme Trading LLC",
    },
    instructions = new
    {
        amount = new { amount = "250.00", currency = "AED" },
    },
    successRedirectUrl = "https://yourapp.com/checkout/complete",
    failureRedirectUrl = "https://yourapp.com/checkout/failed",
};

// Build xSignature from paymentRequest (see Request signing).
var body = JsonSerializer.Serialize(paymentRequest);

using var client = new HttpClient();
var request = new HttpRequestMessage(
    HttpMethod.Post,
    "https://api.sandbox.tryspare.ae/payment-requests");
request.Headers.Add("Authorization", $"Bearer {accessToken}");
request.Headers.Add("x-tenant", "UAE");
request.Headers.Add("x-signature", xSignature);
request.Content = new StringContent(body, Encoding.UTF8, "application/json");

var response = await client.SendAsync(request);
// Parse data.redirectUrl from the JSON response.
paymentRequest := map[string]any{
	"type":              "SingleInstantPayment",
	"creditorType":      "MERCHANT",
	"creditorReference": "Order #9876",
	"merchantReference": "order-9876",
	"purpose":           "ACM",
	"creditorAccount": map[string]string{
		"schemeName":     "IBAN",
		"identification": "10000109010101",
		"name":           "Acme Trading LLC",
	},
	"instructions": map[string]any{
		"amount": map[string]string{"amount": "250.00", "currency": "AED"},
	},
	"successRedirectUrl": "https://yourapp.com/checkout/complete",
	"failureRedirectUrl": "https://yourapp.com/checkout/failed",
}

// Build xSignature from paymentRequest (see Request signing).
body, _ := json.Marshal(paymentRequest)

req, err := http.NewRequest(
	http.MethodPost,
	"https://api.sandbox.tryspare.ae/payment-requests",
	bytes.NewReader(body),
)
req.Header.Set("Authorization", "Bearer "+accessToken)
req.Header.Set("x-tenant", "UAE")
req.Header.Set("Content-Type", "application/json")
req.Header.Set("x-signature", xSignature)

res, err := http.DefaultClient.Do(req)
// Parse data.redirectUrl from the JSON response.

SDK availability

Official server SDKs beyond TypeScript are still under review. The samples above use REST fetch/requests calls that match the public schema. When SDKs ship, confirm they send x-signature over the same JSON body before switching off raw HTTP.

Example success response:

{
  "data": {
    "id": "3c0c121a-da08-45a9-a00b-e3d81004caad",
    "type": "SingleInstantPayment",
    "status": "New",
    "amount": "250.00",
    "currency": "AED",
    "purpose": "ACM",
    "creditorType": "MERCHANT",
    "creditorReference": "Order #9876",
    "merchantReference": "order-9876",
    "merchantId": "03c18731-9c29-4255-bea1-593536387bf3",
    "internalReference": "6GzTwOzLVbF",
    "isCopVerified": false,
    "creditorAccount": {
      "id": "858704f9-db10-40ff-a79d-12e658ec2713",
      "schemeName": "IBAN",
      "identification": "10000109010101",
      "name": "Acme Trading LLC"
    },
    "instructions": {
      "amount": {
        "amount": "250.00",
        "currency": "AED"
      }
    },
    "redirectUrl": "https://web.sandbox.tryspare.ae/pis/6GzTwOzLVbF",
    "successRedirectUrl": "https://yourapp.com/checkout/complete",
    "failureRedirectUrl": "https://yourapp.com/checkout/failed",
    "createdAt": "2026-08-11T09:33:40.037+00:00",
    "updatedAt": "2026-08-11T09:33:40.037+00:00"
  }
}

Redirect the payer to data.redirectUrl to start bank authorization on Spare hosted pages.

Redirect the payer

Send the payer to redirectUrl from the create response. Spare hosted pages walk them through:

  1. Choosing their bank from the supported list.
  2. Reviewing the payment summary (amount, creditor, reference).
  3. Signing in on the bank login page and authorizing the payment.

On the sandbox bank login page, enter a username and password. Use either pair:

UsernamePassword
mitsmits
omar.farsi@testmail.aePIX

Do not skip this step. The payment does not proceed until the payer completes bank authorization.

Handle the return

Success: After authorization, Spare shows a success screen and redirects the payer to your successRedirectUrl. The URL includes referenceId, merchantRef, and sig query parameters. sig is an ES256 JWS of the URL; verify it on your server before fulfilling the order.

Failure: If authorization fails or the payer cancels, Spare shows an error screen and redirects to your failureRedirectUrl when you registered one at create time.

Key takeaways

  • For hosted Single Instant Payment, you integrate primarily with one API: POST /payment-requests.
  • Whitelist IPs and keep the private key server-side; write requests need x-signature.
  • Always send the payer to redirectUrl from the create response; do not skip bank authorization.
  • On return, verify sig on the success redirect URL before fulfilling the order.

On this page