Skip to content
SDK preview. Packages are not publicly published yet — use REST / cURL, or the local development artifacts.

REST & other languages

Create payments over plain HTTP from PHP, Laravel, Java / Spring Boot, Go, Python, Node.js without an SDK, or curl.

The API is plain JSON over HTTPS. You don't need an SDK. Every example below creates a test payment; run them on your server with:

Shell
export PAYTAKA_SECRET_KEY=pt_test_sk_...
export PAYTAKA_API_URL=https://api.paytaka.live
  • Headers: Authorization: Bearer <secret key>, Content-Type: application/json, and an Idempotency-Key (see Idempotency).
  • Amounts are strings: "500.00".

Pick your language:

curl#

create-payment.sh · Shell
#!/usr/bin/env bash
# Create a TEST payment with plain curl. Needs a Test secret key from
# Developers → API keys (it starts with pt_test_sk_). No real money is involved.
set -euo pipefail
: "${PAYTAKA_SECRET_KEY:?Set PAYTAKA_SECRET_KEY to your pt_test_sk_... key}"
: "${PAYTAKA_API_URL:?Set PAYTAKA_API_URL to the PayTaka API base URL you were given}"

curl "$PAYTAKA_API_URL/v1/payments" \
  -X POST \
  -H "Authorization: Bearer $PAYTAKA_SECRET_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"amount":"500.00"}'

Node.js (no SDK)#

create-payment.mjs · JavaScript
// Plain Node.js (18+), no dependencies: create a TEST payment with fetch.
// Run on your SERVER — the secret key must never reach a browser or an app.
import { randomUUID } from "node:crypto";

const apiUrl = process.env.PAYTAKA_API_URL; // the PayTaka API base URL you were given
if (!apiUrl || !process.env.PAYTAKA_SECRET_KEY) throw new Error("Set PAYTAKA_API_URL and PAYTAKA_SECRET_KEY");

const response = await fetch(`${apiUrl}/v1/payments`, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.PAYTAKA_SECRET_KEY}`,
    "Content-Type": "application/json",
    "Idempotency-Key": randomUUID(),
  },
  body: JSON.stringify({ amount: "500.00" }),
});

const payment = await response.json();
if (!response.ok) {
  // Every error has a code, a message and a request_id to quote to support.
  throw new Error(`${payment.error.code}: ${payment.error.message} (${payment.error.request_id})`);
}
console.log(payment.checkout_url);

PHP#

create-payment.php · PHP
<?php
// Plain PHP (cURL extension) — run on your SERVER. Test mode: use a pt_test_sk_ key.
$secretKey = getenv('PAYTAKA_SECRET_KEY');
$apiUrl = getenv('PAYTAKA_API_URL'); // the PayTaka API base URL you were given
if (!$secretKey || !$apiUrl) {
    throw new RuntimeException('Set PAYTAKA_SECRET_KEY and PAYTAKA_API_URL');
}

$ch = curl_init("$apiUrl/v1/payments");
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        "Authorization: Bearer $secretKey",
        'Content-Type: application/json',
        'Idempotency-Key: ' . bin2hex(random_bytes(16)),
    ],
    CURLOPT_POSTFIELDS => json_encode(['amount' => '500.00']),
]);

$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

$payment = json_decode($body, true);
if ($status >= 400) {
    // Every error has a code, a message and a request_id to quote to support.
    throw new RuntimeException("{$payment['error']['code']}: {$payment['error']['message']} ({$payment['error']['request_id']})");
}

header('Location: ' . $payment['checkout_url']); // or echo it / return it to your app

Laravel#

PayTakaController.php · PHP
<?php
// app/Http/Controllers/PayTakaController.php (Laravel 10+)
namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Str;

class PayTakaController extends Controller
{
    // POST /pay — creates a TEST payment and sends the customer to checkout.
    public function pay()
    {
        $response = Http::withToken(config('services.paytaka.secret'))
            ->withHeaders(['Idempotency-Key' => (string) Str::uuid()])
            ->post(config('services.paytaka.url') . '/v1/payments', ['amount' => '500.00'])
            ->throw();

        return redirect()->away($response->json('checkout_url'));
    }

    // POST /paytaka/webhook — exclude this route from CSRF verification.
    public function webhook(Request $request)
    {
        $rawBody = $request->getContent();                          // the RAW body
        $header = (string) $request->header('PayTaka-Signature');
        parse_str(str_replace(',', '&', $header), $parts);
        $timestamp = $parts['t'] ?? '';
        $expected = hash_hmac('sha256', $timestamp . '.' . $rawBody, config('services.paytaka.webhook_secret'));

        abort_unless(
            ctype_digit($timestamp) && abs(time() - (int) $timestamp) <= 300 && hash_equals($expected, $parts['v1'] ?? ''),
            400
        );

        $event = json_decode($rawBody, true);
        if ($event['type'] === 'payment.paid') {
            // Fulfil $event['data']['object']['id']. Deduplicate on $event['id'].
        }

        return response()->json(['received' => true]);
    }
}

Setup:

setup.md · Text
# Laravel setup

`config/services.php`:

```php
'paytaka' => [
    'url' => env('PAYTAKA_API_URL'),
    'secret' => env('PAYTAKA_SECRET_KEY'),
    'webhook_secret' => env('PAYTAKA_WEBHOOK_SECRET'),
],
```

`.env` (server-side only): `PAYTAKA_SECRET_KEY=pt_test_sk_...` and `PAYTAKA_API_URL=<the PayTaka API base URL you were given>`

`routes/web.php`:

```php
Route::post('/pay', [PayTakaController::class, 'pay']);
Route::post('/paytaka/webhook', [PayTakaController::class, 'webhook']);
```

Exclude `paytaka/webhook` from CSRF in `bootstrap/app.php`:
`$middleware->validateCsrfTokens(except: ['paytaka/webhook']);`

Java / Spring Boot#

PayTakaService.java · Java
package com.example.paytaka;

import java.util.Map;
import java.util.UUID;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestClient;

/**
 * Creates PayTaka payments from your Spring Boot SERVER. The secret key comes from
 * configuration (paytaka.secret-key / PAYTAKA_SECRET_KEY) — never hard-code it.
 */
@Service
public class PayTakaService {

    private final RestClient client;

    public PayTakaService(
            @Value("${paytaka.api-url}") String apiUrl,
            @Value("${paytaka.secret-key}") String secretKey) {
        this.client = RestClient.builder()
                .baseUrl(apiUrl)
                .defaultHeader("Authorization", "Bearer " + secretKey)
                .build();
    }

    /** Returns the checkout_url to send the customer (or your mobile app) to. */
    @SuppressWarnings("unchecked")
    public String createPayment(String amount) {
        Map<String, Object> payment = client.post()
                .uri("/v1/payments")
                .header("Idempotency-Key", UUID.randomUUID().toString())
                .body(Map.of("amount", amount))
                .retrieve()
                .body(Map.class);
        return (String) payment.get("checkout_url");
    }
}
application.properties · Properties
# Supply these as environment variables (PAYTAKA_API_URL, PAYTAKA_SECRET_KEY, PAYTAKA_WEBHOOK_SECRET) — never commit them.
paytaka.api-url=${PAYTAKA_API_URL}
paytaka.secret-key=${PAYTAKA_SECRET_KEY}
paytaka.webhook-secret=${PAYTAKA_WEBHOOK_SECRET:}

Supply PAYTAKA_API_URL, PAYTAKA_SECRET_KEY and PAYTAKA_WEBHOOK_SECRET as environment variables; never commit them.

Go#

create_payment.go · Go
package main

import (
	"bytes"
	"crypto/rand"
	"encoding/json"
	"fmt"
	"net/http"
	"os"
)

// createPayment creates a TEST payment with plain net/http and returns its checkout URL.
// Run it on your SERVER; the secret key must never reach a browser or an app.
func createPayment(amount string) (string, error) {
	apiURL := os.Getenv("PAYTAKA_API_URL") // the PayTaka API base URL you were given
	if apiURL == "" || os.Getenv("PAYTAKA_SECRET_KEY") == "" {
		return "", fmt.Errorf("set PAYTAKA_API_URL and PAYTAKA_SECRET_KEY")
	}

	body, _ := json.Marshal(map[string]string{"amount": amount})
	req, err := http.NewRequest(http.MethodPost, apiURL+"/v1/payments", bytes.NewReader(body))
	if err != nil {
		return "", err
	}
	req.Header.Set("Authorization", "Bearer "+os.Getenv("PAYTAKA_SECRET_KEY"))
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Idempotency-Key", newIdempotencyKey())

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()

	var payload struct {
		CheckoutURL string `json:"checkout_url"`
		Error       struct {
			Code      string `json:"code"`
			Message   string `json:"message"`
			RequestID string `json:"request_id"`
		} `json:"error"`
	}
	if err := json.NewDecoder(resp.Body).Decode(&payload); err != nil {
		return "", err
	}
	if resp.StatusCode >= 400 {
		// Every error has a code, a message and a request_id to quote to support.
		return "", fmt.Errorf("%s: %s (%s)", payload.Error.Code, payload.Error.Message, payload.Error.RequestID)
	}
	return payload.CheckoutURL, nil
}

func newIdempotencyKey() string {
	b := make([]byte, 16)
	_, _ = rand.Read(b)
	return fmt.Sprintf("%x", b)
}

Python#

create_payment.py · Python
"""Create a TEST payment with only the Python standard library (3.8+).
Run on your SERVER; the secret key must never reach a browser or an app."""
import json
import os
import urllib.error
import urllib.request
import uuid

api_url = os.environ["PAYTAKA_API_URL"]  # the PayTaka API base URL you were given

request = urllib.request.Request(
    f"{api_url}/v1/payments",
    data=json.dumps({"amount": "500.00"}).encode(),
    method="POST",
    headers={
        "Authorization": f"Bearer {os.environ['PAYTAKA_SECRET_KEY']}",
        "Content-Type": "application/json",
        "Idempotency-Key": str(uuid.uuid4()),
    },
)

try:
    with urllib.request.urlopen(request) as response:
        payment = json.load(response)
except urllib.error.HTTPError as error:
    # Every error has a code, a message and a request_id to quote to support.
    detail = json.load(error)["error"]
    raise SystemExit(f"{detail['code']}: {detail['message']} ({detail['request_id']})")

print(payment["checkout_url"])

The response is the Payment object; send the customer to checkout_url.

Webhooks#

Every language needs the same steps: read the raw body, verify PayTaka-Signature, deduplicate on event.id, respond 2xx. Ready-to-copy verifiers for Node.js, PHP, Java, Go and Python are on the Webhooks page.

Errors#

A failed request returns a JSON error with a code, a message, an optional param, and a request_id:

JSON
{ "error": { "code": "invalid_amount", "message": "Enter a valid amount, e.g. 500.00.", "param": "amount", "request_id": "req_…" } }

See Errors and Troubleshooting. The complete request and response reference is the API reference; the machine-readable source is openapi/paytaka-v1.yaml.