Payments

Connect your point-of-sale (POS) system to Tablescale for in-venue payments when staff complete orders.

How it works

1

Staff taps Pay & Complete on an order in Tablescale.

2

Tablescale sends a payment.requested webhook to your URL with the order total and line items.

3

Your POS charges the customer on the local terminal.

4

Your system reports the result back to Tablescale via the callback API.

5

On success, the order is marked paid and completed. Include payment_method to record cash or card on the session.

Setup checklist

Open Settings → Payments tab
Enter your webhook URL (HTTPS in production)
Generate an API key and webhook secret
Enable test mode and send a test webhook
Verify the webhook signature in your handler
POST the result to /callback with your API key, then go live

Webhook events

Tablescale POSTs JSON to your webhook URL. All requests include signed headers (see Signature verification).

payment.requested

Sent when staff initiates payment or you trigger a test webhook. Contains the amount and line items to charge.

payment.expired

Sent when the payment session times out without a result.

payment.cancelled

Sent when a payment session is cancelled before completion.

Example payload

{
  "event": "payment.requested",
  "id": "evt_abc123",
  "created_at": "2026-06-13T12:00:00Z",
  "data": {
    "payment_session_id": "clx...",
    "order_id": "clx...",
    "order_display_id": "42",
    "amount_cents": 2450,
    "price": "24.50",
    "currency": "EUR",
    "line_items": [
      {
        "name": "Margherita Pizza",
        "quantity": 2,
        "unit_price_cents": 850,
        "options": [{ "name": "Extra cheese", "price_cents": 150 }]
      }
    ],
    "table": "Table 5",
    "expires_at": "2026-06-13T12:02:00Z"
  }
}

Signature verification

Verify that webhooks came from Tablescale using the webhook secret from Settings → Payments.

The snippet below is exemplary JavaScript for decoding and verifying the X-Tablescale-Signature header. Adapt it to your stack — Node.js crypto, Deno, browser Web Crypto, or your language's HMAC utilities.

const crypto = require("crypto");

function verifyTablescaleSignature(payload, signatureHeader, secret) {
  const parts = signatureHeader.split(",");
  const timestamp = parts.find((p) => p.startsWith("t="))?.slice(2);
  const signature = parts.find((p) => p.startsWith("v1="))?.slice(3);
  if (!timestamp || !signature) return false;

  const signed = `${timestamp}.${payload}`;
  const expected = crypto
    .createHmac("sha256", secret)
    .update(signed)
    .digest("hex");

  return crypto.timingSafeEqual(
    Buffer.from(expected, "hex"),
    Buffer.from(signature, "hex")
  );
}

Headers: X-Tablescale-Signature, X-Tablescale-Event-Id, X-Tablescale-Timestamp, X-Tablescale-Test-Mode

Reporting payment results

POST to /api/integrations/payments/callback when your POS finishes processing. Authenticate with your API key in the Authorization header.

Example request

POST /api/integrations/payments/callback
Authorization: Bearer ts_live_...
Content-Type: application/json

{
  "payment_session_id": "clx...",
  "status": "success",
  "payment_method": "CARD",
  "external_ref": "pos-txn-12345"
}

Request body fields

payment_session_idRequired
string

The payment session ID from the webhook payload or test integration panel.

statusRequired
successfail

Whether the POS payment succeeded or failed.

external_refOptional
string

Your POS transaction or receipt reference, if you want it stored on the session.

failure_reasonOptional
string

Human-readable reason when status is fail. Ignored for success callbacks.

payment_methodOptional
CASHCARD

How the customer paid. Stored on the payment session when status is success. Defaults to CASH. Session method values: CASH, CARD (from callback), MANUAL_CASH, MANUAL_CARD (staff override via UI).

Duplicate callbacks with the same session and status are idempotent and return the existing result. All payment activity appears in Settings → Payments → Payment logs, including outbound webhooks, inbound callbacks (callback.success / callback.fail), and internal state changes (payment.success / payment.fail). The Mode column in Payment logs shows Live or Test. Error codes: 400, 401, 404, 409.

Polling session status

If your POS receives the payment.requested webhook but prefers not to call the callback endpoint — or wants to confirm the final status after reporting — poll the session using the payment_session_id from the webhook payload:

GET /api/integrations/payments/sessions/{payment_session_id}
Authorization: Bearer ts_live_...

Polling does not replace webhooks: Tablescale has no list-or-discover endpoint, so your system must receive the initial webhook (or use a relay that forwards the session ID) to learn which session to poll.

Cancelling a payment

POST /api/integrations/payments/sessions/{payment_session_id}/cancel
Authorization: Bearer ts_live_...

Test mode

Test mode sends dummy payment sessions that do not affect live orders. All test webhooks include X-Tablescale-Test-Mode: true.

From Settings → Payments → Test integration: click Send test webhook, then POST the result to /api/integrations/payments/callback using your API key and the returned payment_session_id.

# After sending a test webhook, report success:
curl -X POST https://your-tablescale-domain.com/api/integrations/payments/callback \
  -H "Authorization: Bearer ts_test_..." \
  -H "Content-Type: application/json" \
  -d '{"payment_session_id":"SESSION_ID","status":"success"}'

Rate limits

Test tools in Settings → Payments are rate limited per organization (10 sends per minute). Live production endpoints (/callback, /sessions/*) are not rate limited. When exceeded, you receive 429 Too Many Requests with a Retry-After header.

{ "error": "Rate limit exceeded" }

Tip: reuse the test session ID from a single webhook instead of sending repeated test webhooks in a loop.

Best practices

  • Respond to webhooks within 10 seconds (return 2xx quickly, process async if needed).
  • Verify webhook signatures before processing.
  • Use the payment_session_id to correlate requests — Tablescale may retry failed deliveries.
  • Handle idempotent callbacks — your endpoint may receive the same result more than once.

Security

Webhook URLs must use HTTPS in production.

Keep your API key and webhook secret confidential. Rotate them if compromised.

Tablescale does not handle card data — your POS system processes payments directly. You remain responsible for PCI compliance on your side.