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:
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 anIdempotency-Key(see Idempotency). - Amounts are strings:
"500.00".
Pick your language:
curl#
#!/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)#
// 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#
<?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 appLaravel#
<?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:
# 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#
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");
}
}# 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#
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 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:
{ "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.