API reference

API reference

Akara exposes an inbound endpoint to create deposit requests and signed outbound webhooks for state changes. All requests and responses are JSON.

Base URL & authentication

Endpoints live under your Supabase Edge Functions host. The exact URL is shown in Dashboard → Integrations:

https://<your-project>.supabase.co/functions/v1

Authenticate inbound requests with the API key from Dashboard → Integrations:

Authorization: Bearer akara_YOUR_API_KEY

Keys are stored only as a SHA-256 hash — Akara cannot recover a lost key, so regenerate it if it leaks. Regenerating immediately invalidates the previous key.


Create a deposit request (inbound)

Have your PMS create a deposit request. Akara emails the guest a secure approval link and shows the request in the merchant dashboard.

POST /functions/v1/pms-webhook

Request body

FieldTypeRequiredDescription
guest_emailstringYesGuest email — receives the deposit link.
booking_idstringNoExternal booking ID. Used for idempotency (see below).
guest_namestringNoGuest name. Defaults to "Guest".
amountnumberNoDeposit amount. Defaults to 0.
currencystringNoISO currency code. Defaults to "AED".

Responses

StatusMeaningBody
201Created{ deposit_request_id, deposit_link }
200Idempotent — a request already exists for this booking_id{ deposit_request_id, message }
400Invalid body (e.g. missing guest_email){ error }
401Missing or invalid API key{ error }
405Method not allowed (use POST){ error }

Idempotency

Pass a stable booking_id. If a deposit request already exists for the same workspace and booking_id, Akara returns 200 with the existing deposit_request_id instead of creating a duplicate. Retries are therefore safe.

Example

curl -X POST "https://YOUR_PROJECT.supabase.co/functions/v1/pms-webhook" \
  -H "Authorization: Bearer akara_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "booking_id": "bk-12345",
    "guest_name": "Jane Doe",
    "guest_email": "[email protected]",
    "amount": 1500,
    "currency": "AED"
  }'

Webhooks (outbound)

Add an endpoint in Dashboard → Webhooks and Akara POSTs a signed JSON event to your URL whenever a selected event occurs. Verify every request with the secret shown when you created the endpoint.

Headers

HeaderDescription
Content-Typeapplication/json
X-Akara-EventEvent type, e.g. deposit.approved
X-Akara-Signaturesha256=<hex> — HMAC-SHA256 of the raw body, keyed with your secret

Payload

{
  "event": "deposit.approved",
  "created_at": "2026-07-23T12:00:00.000Z",
  "data": { /* the deposit_requests or claims row, snake_case keys */ }
}

Event types

EventWhendata
deposit_request.createdA new deposit request was createdFull deposit_requests row
deposit.approvedGuest approved the depositRow (status Active)
deposit.disputedGuest disputed the depositRow (status Disputed)
deposit.releasedMerchant released the depositRow (status Released)
deposit.claimedMerchant claimed the depositRow (status Claimed)
deposit.failedPayment failed (guest can retry)Row (status Failed)
claim.submittedMerchant submitted a claimFull claims row
claim.status_changedClaim moved to Approved or RejectedFull claims row

Verifying the signature

  1. Take the raw request body, before JSON parsing.
  2. Compute HMAC-SHA256 with your secret as the key and the raw body as the message.
  3. Hex-encode it and compare against X-Akara-Signature, which is sha256=<hex>.
  4. Always compare in constant time.
const crypto = require('crypto')

function verifySignature(rawBody, signatureHeader, secret) {
  const expected = 'sha256=' + crypto.createHmac('sha256', secret).update(rawBody).digest('hex')
  return crypto.timingSafeEqual(Buffer.from(signatureHeader), Buffer.from(expected))
}

app.post('/webhooks/akara', express.raw({ type: 'application/json' }), (req, res) => {
  const sig = req.headers['x-akara-signature']
  if (!verifySignature(req.body.toString(), sig, process.env.AKARA_WEBHOOK_SECRET)) {
    return res.status(401).send('Invalid signature')
  }
  res.status(200).send('OK')
})
import hmac, hashlib

def verify_signature(raw_body: bytes, signature_header: str, secret: str) -> bool:
    expected = 'sha256=' + hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(signature_header, expected)

Delivery best practices


Errors & status codes

Errors return a JSON body of the shape { "error": "message" }.

CodeMeaning
200OK / idempotent hit
201Resource created
400Invalid request body
401Missing or invalid API key
405Method not allowed
500Unexpected server error — safe to retry with the same booking_id