Server-to-Server (S2S)
The S2S integration allows you to send card details directly to TurnStay’s API instead of redirecting to the hosted payment page. It uses a segregated Secure Card Service (SCS) environment for handling card data.
S2S must be enabled for your account. Contact TurnStay to enable this functionality. S2S is currently available for Virtual Card payments.
When to use S2S vs hosted checkout
Section titled “When to use S2S vs hosted checkout”| Hosted Checkout | S2S | |
|---|---|---|
| Card data | Customer enters on TurnStay’s page | You send card details via API |
| PCI scope | Minimal — you never see card data | Higher — you handle card data server-side |
| Use case | Customer-facing checkout | Virtual cards, automated payments, MOTO |
| 3DS | Handled automatically | SCS returns next steps if required |
| Setup | Available by default | Must be enabled per account |
How it works — two steps
Section titled “How it works — two steps”- Create a Payment Intent on TurnStay with
payment_type: "Virtual Card". The response includes anext_actionsarray with SCS URLs. - Complete the payment via the SCS link — either embed the form (lower PCI) or call the SCS confirm API with card details (full S2S).
Step 1 — Create a Payment Intent
Section titled “Step 1 — Create a Payment Intent”POST /api/v1/payments/intentSame endpoint as the hosted checkout, but set payment_type to "Virtual Card".
import requests
resp = requests.post( "https://staging.turnstay.com/api/v1/payments/intent", headers={"Authorization": "Bearer sk_test_YOUR_KEY"}, json={ "account_id": YOUR_ACCOUNT_ID, "billing_amount": 10000, "billing_currency": "ZAR", "checkin_date": "2026-12-12", "customer_phone_number": "+27123456789", "customer_email": "test@example.com", "payment_type": "Virtual Card", },)data = resp.json()action_url = data["next_actions"][0]["url"]The response includes a next_actions array:
{ "lookup_id": "pi_abc123def456", "status": { "name": "INITIALIZED" }, "next_actions": [ { "type": "Tokenize Card", "url": "https://secure-card-service.pci-staging.turnstay.com/api/v1/card/tokenize?..." }, { "type": "Confirm Payment", "url": "https://staging.turnstay.com/api/v1/payments/intent/pay?..." } ]}Step 2 — Complete payment via SCS
Section titled “Step 2 — Complete payment via SCS”Option A: Embed or redirect (recommended)
Section titled “Option A: Embed or redirect (recommended)”Embed the SCS form URL in an iframe or redirect the customer. This is the lowest PCI scope option — SCS handles tokenization, 3DS, and provider interactions.
print(f"Redirect customer to: {action_url}")Option B: Full S2S via API
Section titled “Option B: Full S2S via API”If you need to send card details server-side, POST to the SCS confirm endpoint:
POST {SCS_URL}/api/v1/public/payment/intent/{scs_intent_id}/confirm| Field | Type | Description |
|---|---|---|
cardholder_name | string | Name on card |
card_number | string | Full card number |
expiry_month | int | 1–12 |
expiry_year | int | 4-digit year |
cvc | string | 3 or 4 digit CVC |
customer_email | string | Customer’s email |
customer_phone_number | string | International format |
from urllib.parse import urlparse, parse_qs
parsed = urlparse(action_url)scs_intent_id = parsed.path.rsplit('/', 1)[-1]client_secret = parse_qs(parsed.query).get('client_secret', [None])[0]
SCS_URL = "https://secure-card-service.pci-staging.turnstay.com"confirm_url = f"{SCS_URL}/api/v1/public/payment/intent/{scs_intent_id}/confirm"
result = requests.post( confirm_url, json={ "cardholder_name": "John Doe", "card_number": "4242424242424242", "expiry_month": 12, "expiry_year": 2026, "cvc": "123", "customer_email": "john.doe@example.com", "customer_phone_number": "+27123456789", }, params={"client_secret": client_secret} if client_secret else {},)Security considerations
Section titled “Security considerations”- Prefer Option A (embed/redirect) to minimize your PCI scope.
- Only use Option B (full S2S) if your infrastructure is PCI-compliant for handling card data.
- Never log or store raw card numbers.
- Keep Bearer tokens secret and rotate regularly.