{"templateId":"markdown","sharedDataIds":{"sidebar":"sidebar-sidebars.yaml"},"props":{"metadata":{"markdoc":{"tagList":[]},"type":"markdown"},"seo":{"title":"Accept card payments server-to-server","siteUrl":"https://docs.glomo.one","llmstxt":{"title":"Glomo developer documentation","description":"Cross-border payments APIs, organised by product. Each product section carries both the explanation of its objects and the job-shaped integration guides that set them up.","details":{"content":"How to use these docs:\n\n- Get started is the front door. It has one section per integration\n  (what you're building), each with the API calls in order and the guide that builds it, plus\n  how to get API keys.\n- The product sections - Payin, Payout, Multi-currency accounts,\n  Verification, Request for Information (RFI) - each hold two kinds of page.\n  Pages titled as a noun (\"Subscriptions\", \"Bank Transfer\") explain what\n  an object is. Pages titled as a job (\"Set up recurring subscriptions\",\n  \"Collect LRS remittances from India\") are complete integration flows\n  and live one level under the object they set up. Match a user's goal\n  against the job-shaped titles first.\n- Reports and Identity and Access Management are the two subjects with no\n  API counterpart. There are no endpoints for reports, users, roles or\n  invites; both pages document the merchant dashboard. A dashboard\n  procedure for something the API also does (a refund, a payout) is NOT\n  here - it stays in the product that owns it.\n- API reference is generated from OpenAPI and carries endpoint detail.\n- The OpenAPI description itself is published as a single self-contained\n  document at /openapi.yaml, and the same document as JSON at\n  /openapi.json. Every $ref is resolved into it, so one fetch is the\n  whole contract: 61 paths, 81 operations, 68 schemas - the same\n  endpoints the API reference pages render. Fetch that instead of\n  reading the 129 pages under /api-reference/.\n- Every page below, in full, is one document at /llms-full.txt. Fetch that\n  instead of the pages one by one. It leaves out the API reference;\n  /openapi.yaml is that.\n- Developer resources covers what every integration touches and no single\n  product owns: auth, errors, pagination, rate limits, webhooks,\n  versioning, supported countries, quotes, the MCP server for AI\n  agents, and the client SDKs.\n"},"sections":[{"title":"Get started","description":"The front door. The ordered API calls for each integration and the guide that delivers it, how to obtain API keys, and the platform glossary.","includeFiles":["**/get-started/**"]},{"title":"Payin","description":"Collecting money: customers, orders, checkout, payment links, payment methods (cards, bank transfer, pay via bank), refunds, disputes, purpose codes, fee models, custom fields, subscriptions, and resident India remittance under LRS. Includes the flows that set each up - bank transfers, subscriptions, LRS remittances, and server-to-server card payments.","includeFiles":["**/payin/**"]},{"title":"Payout","description":"Sending money: the payout life cycle, queued payouts, creating and cancelling a payout, rails, beneficiaries, purpose codes, and the action required state. Includes the flow that sends a payout over the API.","includeFiles":["**/payout/**"]},{"title":"Multi-currency accounts","description":"Holding money: balances, balance conversion, adding balance, and withdrawing balance.","includeFiles":["**/multi-currency-account/**"]},{"title":"Verification","description":"Proving who someone is and that an account is theirs: KYC, bank account validation, and third-party verification (TPV) with the flow that verifies a payer's bank account.","includeFiles":["**/verification/**"]},{"title":"Request for Information (RFI)","description":"Responding to a compliance request for information, per object - payment link, order, payment, payout - plus compliance reviews and RFIs raised on already-successful payments.","includeFiles":["**/request-for-information/**"]},{"title":"Nivo (Capital markets)","description":"Nivo, Glomo's capital markets suite (Nivo for AMCs, Nivo for Distributors, and overseas investing for brokers and wealth platforms). Mutual fund investments run as payment sessions: the fund, distributor, scheme, plan, ARN and folio roles; the distributor's relationship with a fund and what it fixes; and the flow that opens a session as a distributor.","includeFiles":["**/capital-markets/**"]},{"title":"Reports","description":"The downloadable and scheduled reports a merchant pulls from the dashboard for reconciliation, compliance and treasury: payment, subscription, settlement breakup, balance statement and KYC reports, plus scheduling one for recurring email delivery and customizing the fields and column order of a report. Dashboard only - there is no reports API.","includeFiles":["**/reports/**"]},{"title":"Identity and Access Management","description":"Administering who can use a merchant account: one set of credentials across multiple MIDs, switching between them without logging out, the Admin / Member / Sales Partner roles and what each may see and do, and inviting a new member. Dashboard only - there is no users, roles or invites API.","includeFiles":["**/access-management/**"]},{"title":"Settlement holidays","description":"The settlement holiday calendar for 2026 - the Indian and US bank and FX holidays on which payin, payout and settlement to the merchant's bank account may be delayed.","includeFiles":["**/settlement-holidays/**"]},{"title":"API reference","description":"Endpoint reference generated from the Glomo OpenAPI description.","includeFiles":["**/api-reference/**"]},{"title":"Developer resources","description":"What every integration touches and no single product owns: auth, errors, pagination, rate limits, webhooks, versioning, API validations, security, supported countries, quotes, connecting an AI agent to the Glomo MCP server, and the client SDKs - React Native, Flutter, Android and the Unified Web SDK, with per-version references and changelogs. Reports and Identity and Access Management used to be listed here and are now their own sections.","includeFiles":["**/platform/**"]}]}},"dynamicMarkdocComponents":[],"compilationErrors":[],"ast":{"$$mdtype":"Tag","name":"article","attributes":{},"children":[{"$$mdtype":"Tag","name":"Heading","attributes":{"level":1,"id":"accept-card-payments-server-to-server","__idx":0},"children":["Accept card payments server-to-server"]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"overview","__idx":1},"children":["Overview"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Server-to-Server (S2S) integration allows you to collect card details on your platform and process payments through Glomo's infrastructure. This integration is designed for PCI DSS certified merchants who want to maintain control over the payment experience while leveraging Glomo's cross-border payment processing capabilities."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"prerequisites","__idx":2},"children":["Prerequisites"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["PCI DSS Certification"]},": You must be PCI DSS certified to collect and transmit card details"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Glomo Account"]},": Active merchant account"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["API Keys"]},": Secret keys for both sandbox (test) and production (live) environments"]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"environment-configuration","__idx":3},"children":["Environment Configuration"]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"api-endpoint","__idx":4},"children":["API Endpoint"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["All API requests use the same base URL for both environments:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"header":{"controls":{"copy":{}}},"source":"https://api.glomopay.com\n"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"authentication","__idx":5},"children":["Authentication"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The secret key in your Authorization header determines the environment:"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Sandbox Key"]}," → Creates orders and payments in test mode"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Production Key"]}," → Creates orders and payments in live mode"]}]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"header":{"controls":{"copy":{}}},"source":"Authorization: Bearer YOUR_SECRET_KEY\n"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"getting-your-api-keys","__idx":6},"children":["Getting Your API Keys"]},{"$$mdtype":"Tag","name":"ol","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Navigate to: https://app.glomopay.com/api-keys-and-webhooks/api-keys"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Use the toggle to switch between Test Mode and Live Mode"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Reveal / Regenerate the API key"]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["For more information on Secret Keys, please refer to ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/platform/authentication"},"children":["Authentication"]}]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"integration-flow","__idx":7},"children":["Integration Flow"]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"step-1-before-accepting-payment-you-need-to-create-an-order-or-a-subscription-in-glomos-system","__idx":8},"children":["Step 1: Before accepting payment, you need to create an order or a subscription in Glomo's system."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"one-time-payment---orders","__idx":9},"children":["One Time Payment - Orders"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["To learn what an order is, refer to ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/payin/order"},"children":["Orders"]},"."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["For the full API reference, refer to ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/api-reference/openapi/orders/createorder"},"children":["Create Order API"]},"."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"subscriptions","__idx":10},"children":["Subscriptions"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["With ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["as_presented"]}," subscriptions, subsequent payments are merchant-initiated — no customer action is required. Card details are automatically carried over from the initial payment, so the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["card"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["callback_url"]},", and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["method"]}," fields can be omitted."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["To learn what a subscription is, refer to: ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/payin/subscriptions"},"children":["Subscriptions"]},"."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["For the full API reference, refer to: ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/api-reference/openapi/subscription/createsubscription"},"children":["Create a Subscription API"]},"."]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"step-2-initiate-payment","__idx":11},"children":["Step 2: Initiate Payment"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Once you have the order_id/subscription_id generated from Step 1, submit card details to initiate payment."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Request Body:"]}]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"bash","header":{"controls":{"copy":{}}},"source":"curl -i -X POST \\\n  https://api.glomopay.com/api/v1/payment \\\n  -H 'Authorization: Bearer <YOUR_JWT_HERE>' \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n    \"order_id\": \"order_68c00b7btsthf\",\n    \"subscription_id\": \"sub_5JU9yv0lGSUP\",\n    \"method\": \"card\",\n    \"sequence\": \"initial\",\n    \"card\": {\n      \"holder_name\": \"John Doe\",\n      \"number\": \"4111111111111111\",\n      \"expiry_month\": \"09\",\n      \"expiry_year\": \"30\",\n      \"cvv\": \"123\"\n    },\n    \"callback_url\": \"https://server.yoursite.com/payment/callback\",\n    \"notes\": {\n      \"internal_ref\": \"ref_12345\"\n    }\n  }'\n","lang":"bash"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Request Body Parameters:"]}]},{"$$mdtype":"Tag","name":"div","attributes":{"className":"md-table-wrapper"},"children":[{"$$mdtype":"Tag","name":"table","attributes":{"className":"md"},"children":[{"$$mdtype":"Tag","name":"thead","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Parameter"},"children":["Parameter"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Type"},"children":["Type"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Required"},"children":["Required"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Description"},"children":["Description"]}]}]},{"$$mdtype":"Tag","name":"tbody","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["order_id"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["string"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Yes*"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Order ID. Either ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["order_id"]}," or ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["subscription_id"]}," must be provided, but not both."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["subscription_id"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["string"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Yes*"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Subscription ID. Either ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["order_id"]}," or ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["subscription_id"]}," must be provided, but not both."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["method"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["string"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Yes (initial)"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Payment method. Must be ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["card"]},". Not required for subsequent payments (defaults to ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["card"]},")."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["sequence"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["string"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["No"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Payment sequence: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["initial"]}," (default) or ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["subsequent"]},". Use ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["subsequent"]}," for merchant-initiated payments on ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["as_presented"]}," subscriptions."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["card"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["object"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Yes (initial)"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Card details (holder_name, number, expiry_month, expiry_year, cvv). Required for initial payments. Not required for subsequent payments — the card from the last successful payment is reused."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["callback_url"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["string"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Yes (initial)"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["HTTPS URL for redirect after payment. Required for initial payments. Not required for subsequent payments as there is no customer redirect involved."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["request_id"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["string"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Yes (subsequent)"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Idempotency key. Required for subsequent payments."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["amount"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["integer"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Yes (subsequent)"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Payment amount in minor units. Required for subsequent subscription payments. Cannot exceed the subscription's ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["max_amount"]},"."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["currency"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["string"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Yes (subsequent)"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Currency code. Must match the subscription's currency for subsequent payments."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["notes"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["object"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["No"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Additional key-value metadata."]}]}]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Response:"]}]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"json","header":{"controls":{"copy":{}}},"source":"{\n  \"payment_id\": \"payt_691eeb9aV79Uk\",\n  \"status\": \"pending\",\n  \"next_steps\": [\n    {\n      \"action\": \"redirect\",\n      \"payload\": {\n        \"url\": \"https://secure.glomopay.com?paymentId=payt_123&authToken=ey...&redirectUrl=encoded_url\"\n      }\n    },\n    {\n      \"action\": \"poll\",\n      \"payload\": {\n        \"url\": \"https://api.glomopay.com/api/v1/payment/payt_691eeb9aV79Uk\",\n        \"interval_in_ms\": 5000\n      }\n    }\n  ]\n}\n","lang":"json"},"children":[]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"step-3-handle-redirect","__idx":12},"children":["Step 3: Handle Redirect"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The response contains a redirect URL where the user needs to complete authentication (3DS, OTP, etc.)."]},{"$$mdtype":"Tag","name":"div","attributes":{"className":"md-table-wrapper"},"children":[{"$$mdtype":"Tag","name":"table","attributes":{"className":"md"},"children":[{"$$mdtype":"Tag","name":"thead","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Method"},"children":["Method"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Use Case"},"children":["Use Case"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Implementation"},"children":["Implementation"]}]}]},{"$$mdtype":"Tag","name":"tbody","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["New Tab"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Web"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["window.open(redirectUrl, '_blank')"]}]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Same Tab"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Web"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["window.location.href = redirectUrl"]}]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["WebView"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Mobile apps"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Open URL in native WebView component"]}]}]}]}]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"step-4-handle-callback","__idx":13},"children":["Step 4: Handle Callback"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["After payment processing, Glomo redirects the user back to your ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["callback_url"]}," with query parameters."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Callback URL Format:"]}]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"header":{"controls":{"copy":{}}},"source":"https://server.yoursite.com/payment/callback?order_id=order_XXX&payment_id=payt_XXX&status=success&signature=abc123\n"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Query Parameters:"]}]},{"$$mdtype":"Tag","name":"div","attributes":{"className":"md-table-wrapper"},"children":[{"$$mdtype":"Tag","name":"table","attributes":{"className":"md"},"children":[{"$$mdtype":"Tag","name":"thead","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Parameter"},"children":["Parameter"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Description"},"children":["Description"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Values"},"children":["Values"]}]}]},{"$$mdtype":"Tag","name":"tbody","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["order_id"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["The order identifier"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["order_XXX"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["payment_id"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["The payment identifier"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["payt_XXX"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["status"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Payment outcome"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["success, failed"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["signature"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["HMAC signature for verification"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["SHA-256 hash"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["error"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Something went wrong in the payment journey"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["message"]}]}]}]}]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"step-5-verify-signature","__idx":14},"children":["Step 5: Verify Signature"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Always verify the signature on your server to ensure the callback is authentic. The callback URL should point to your server, where you can perform this verification. The necessary information for verification will be sent in the query parameters. The response of this API call should ideally be a redirect to your frontend."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The signature is the hex HMAC-SHA256 of ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["order_id|payment_id|status"]},". Compute it with each of your active secret keys, and accept the callback if any one of them matches. Your active secret keys are every secret key on your account that has not expired. Use a constant-time comparison."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The signature may be made with any one of your active secret keys, and it can differ between payments. Don't assume it is your newest key. If you hold more than one active key, checking only one of them can reject a genuine payment. When you revoke a key, or it expires, remove it from your list. A stale key left in the list opens no security hole, but keep the list matching the keys in your dashboard."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Each sample loads your active secret keys from a comma-separated ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["GLOMO_SECRET_KEYS"]}," environment variable. It trims each key, drops empty entries, and stops with an error if no key is left. An empty key must never count as a match: anyone can compute the signature for an empty key, so it would let a forged callback through."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Sample Code:"]}]},{"$$mdtype":"Tag","name":"details","attributes":{},"children":[{"$$mdtype":"Tag","name":"summary","attributes":{},"children":["JavaScript"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"javascript","header":{"controls":{"copy":{}}},"source":"const express = require('express');\nconst crypto = require('crypto');\nconst router = express.Router();\n\n// Every active Glomo secret key, comma-separated (store in environment variables)\nconst GLOMO_SECRET_KEYS = (process.env.GLOMO_SECRET_KEYS || '')\n  .split(',')\n  .map((k) => k.trim())\n  .filter(Boolean);\nif (GLOMO_SECRET_KEYS.length === 0) throw new Error('GLOMO_SECRET_KEYS is not set');\n\n/**\n * Check the signature against each active secret key\n */\nfunction verifySignature(orderId, paymentId, status, signature, secretKeys) {\n  const data = `${orderId}|${paymentId}|${status}`;\n  const received = Buffer.from(signature || '');\n  return secretKeys.some((secret) => {\n    if (!secret) return false; // an empty key never matches\n    const expected = Buffer.from(crypto.createHmac('sha256', secret).update(data).digest('hex'));\n    return expected.length === received.length && crypto.timingSafeEqual(expected, received);\n  });\n}\n\n/**\n * Glomo Payment Callback Handler\n */\nrouter.get('/payment/callback', (req, res) => {\n  // Extract query parameters\n  const { order_id, payment_id, status, signature } = req.query;\n\n  // Verify signature\n  if (!verifySignature(order_id, payment_id, status, signature, GLOMO_SECRET_KEYS)) {\n    console.error('Signature verification failed');\n    return res.redirect(`/payment/error?message=Invalid signature`);\n  }\n\n  // Signature is valid - proceed based on payment status\n  console.log('Payment verified successfully');\n\n  // Redirect based on payment status\n  if (status === 'success') {\n    return res.redirect(`/payment/success?order_id=${order_id}&payment_id=${payment_id}`);\n  } else if (status === 'failed') {\n    return res.redirect(`/payment/failed?order_id=${order_id}`);\n  } else {\n    return res.redirect(`/payment/error?order_id=${order_id}`);\n  }\n});\n\nmodule.exports = router;\n","lang":"javascript"},"children":[]}]},{"$$mdtype":"Tag","name":"details","attributes":{},"children":[{"$$mdtype":"Tag","name":"summary","attributes":{},"children":["PHP"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"php","header":{"controls":{"copy":{}}},"source":"<?php\n\n/**\n * Check the signature against each active secret key\n */\nfunction verifySignature($orderId, $paymentId, $status, $signature, array $secretKeys) {\n    $data = \"$orderId|$paymentId|$status\";\n    foreach ($secretKeys as $secret) {\n        if ((string) $secret === '') {\n            continue; // an empty key never matches\n        }\n        if (hash_equals(hash_hmac('sha256', $data, $secret), (string) $signature)) {\n            return true;\n        }\n    }\n    return false;\n}\n\n// Every active Glomo secret key, comma-separated, from your config\n$glomoSecretKeys = array_values(array_filter(array_map('trim', explode(',', (string) getenv('GLOMO_SECRET_KEYS')))));\nif (count($glomoSecretKeys) === 0) {\n    throw new RuntimeException('GLOMO_SECRET_KEYS is not set');\n}\n\n// Verify signature\nif (!verifySignature($orderId, $paymentId, $status, $signature, $glomoSecretKeys)) {\n    // Signature verification failed\n}\n","lang":"php"},"children":[]}]},{"$$mdtype":"Tag","name":"details","attributes":{},"children":[{"$$mdtype":"Tag","name":"summary","attributes":{},"children":["Ruby"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"ruby","header":{"controls":{"copy":{}}},"source":"require 'openssl'\n\n# Check the signature against each active secret key\ndef verify_signature(order_id, payment_id, status, signature, secret_keys)\n  data = \"#{order_id}|#{payment_id}|#{status}\"\n  secret_keys.any? do |secret|\n    next false if secret.to_s.empty? # an empty key never matches\n    OpenSSL.secure_compare(OpenSSL::HMAC.hexdigest('SHA256', secret, data), signature.to_s)\n  end\nend\n\n# Every active Glomo secret key, comma-separated, from your config\nGLOMO_SECRET_KEYS = ENV.fetch('GLOMO_SECRET_KEYS', '').split(',').map(&:strip).reject(&:empty?)\nraise 'GLOMO_SECRET_KEYS is not set' if GLOMO_SECRET_KEYS.empty?\n\n# Verify signature\nunless verify_signature(order_id, payment_id, status, signature, GLOMO_SECRET_KEYS)\n  # Signature verification failed\nend\n","lang":"ruby"},"children":[]}]},{"$$mdtype":"Tag","name":"details","attributes":{},"children":[{"$$mdtype":"Tag","name":"summary","attributes":{},"children":["Go"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"go","header":{"controls":{"copy":{}}},"source":"import (\n    \"crypto/hmac\"\n    \"crypto/sha256\"\n    \"encoding/hex\"\n    \"errors\"\n    \"fmt\"\n    \"log\"\n    \"os\"\n    \"strings\"\n)\n\n// loadSecretKeys reads every active Glomo secret key from a comma-separated value\nfunc loadSecretKeys(value string) ([]string, error) {\n    var keys []string\n    for _, key := range strings.Split(value, \",\") {\n        key = strings.TrimSpace(key)\n        if key != \"\" {\n            keys = append(keys, key)\n        }\n    }\n    if len(keys) == 0 {\n        return nil, errors.New(\"GLOMO_SECRET_KEYS is not set\")\n    }\n    return keys, nil\n}\n\n// verifySignature checks the signature against each active secret key\nfunc verifySignature(orderID, paymentID, status, signature string, secretKeys []string) bool {\n    data := fmt.Sprintf(\"%s|%s|%s\", orderID, paymentID, status)\n\n    for _, secret := range secretKeys {\n        if secret == \"\" {\n            continue // an empty key never matches\n        }\n        h := hmac.New(sha256.New, []byte(secret))\n        h.Write([]byte(data))\n        expected := hex.EncodeToString(h.Sum(nil))\n\n        if hmac.Equal([]byte(expected), []byte(signature)) {\n            return true\n        }\n    }\n    return false\n}\n\n// Every active Glomo secret key, comma-separated, from your config\nglomoSecretKeys, err := loadSecretKeys(os.Getenv(\"GLOMO_SECRET_KEYS\"))\nif err != nil {\n    log.Fatal(err)\n}\n\n// Verify signature\nif !verifySignature(orderID, paymentID, status, signature, glomoSecretKeys) {\n    // Signature verification failed\n}\n","lang":"go"},"children":[]}]},{"$$mdtype":"Tag","name":"details","attributes":{},"children":[{"$$mdtype":"Tag","name":"summary","attributes":{},"children":["Java"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"java","header":{"controls":{"copy":{}}},"source":"import java.nio.charset.StandardCharsets;\nimport java.security.MessageDigest;\nimport java.util.ArrayList;\nimport java.util.List;\nimport javax.crypto.Mac;\nimport javax.crypto.spec.SecretKeySpec;\n\n// Every active Glomo secret key, comma-separated, from your config\nprivate static final List<String> GLOMO_SECRET_KEYS = loadSecretKeys(System.getenv(\"GLOMO_SECRET_KEYS\"));\n\npublic static List<String> loadSecretKeys(String value) {\n    List<String> keys = new ArrayList<>();\n    if (value != null) {\n        for (String key : value.split(\",\")) {\n            if (!key.trim().isEmpty()) keys.add(key.trim());\n        }\n    }\n    if (keys.isEmpty()) throw new IllegalStateException(\"GLOMO_SECRET_KEYS is not set\");\n    return keys;\n}\n\n// Call as verifySignature(orderId, paymentId, status, signature, GLOMO_SECRET_KEYS)\npublic static boolean verifySignature(String orderId, String paymentId, String status,\n                                      String signature, List<String> secretKeys) {\n    if (signature == null) return false;\n    try {\n        byte[] data = (orderId + \"|\" + paymentId + \"|\" + status).getBytes(StandardCharsets.UTF_8);\n        byte[] received = signature.getBytes(StandardCharsets.UTF_8);\n\n        for (String secret : secretKeys) {\n            if (secret == null || secret.isEmpty()) continue; // an empty key never matches\n\n            Mac mac = Mac.getInstance(\"HmacSHA256\");\n            mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), \"HmacSHA256\"));\n\n            StringBuilder hex = new StringBuilder();\n            for (byte b : mac.doFinal(data)) hex.append(String.format(\"%02x\", b));\n\n            if (MessageDigest.isEqual(hex.toString().getBytes(StandardCharsets.UTF_8), received)) {\n                return true;\n            }\n        }\n        return false;\n    } catch (Exception e) {\n        return false;\n    }\n}\n","lang":"java"},"children":[]}]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"step-6-handle-webhooks","__idx":15},"children":["Step 6: Handle Webhooks"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Webhooks deliver server-to-server notifications about payment events directly to your backend."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["For complete webhook implementation details:"," ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/platform/webhooks"},"children":["Webhooks Documentation"]}]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"testing","__idx":16},"children":["Testing"]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"test-cards","__idx":17},"children":["Test Cards"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Use these cards in sandbox mode to simulate different scenarios:"," ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/payin/payment-methods/cards#test-card-details"},"children":["Test Card Details"]}]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["For technical support, contact your integration manager or reach out via the merchant dashboard."]}]},"headings":[{"value":"Accept card payments server-to-server","id":"accept-card-payments-server-to-server","depth":1},{"value":"Overview","id":"overview","depth":2},{"value":"Prerequisites","id":"prerequisites","depth":2},{"value":"Environment Configuration","id":"environment-configuration","depth":2},{"value":"API Endpoint","id":"api-endpoint","depth":3},{"value":"Authentication","id":"authentication","depth":3},{"value":"Getting Your API Keys","id":"getting-your-api-keys","depth":3},{"value":"Integration Flow","id":"integration-flow","depth":2},{"value":"Step 1: Before accepting payment, you need to create an order or a subscription in Glomo's system.","id":"step-1-before-accepting-payment-you-need-to-create-an-order-or-a-subscription-in-glomos-system","depth":3},{"value":"One Time Payment - Orders","id":"one-time-payment---orders","depth":3},{"value":"Subscriptions","id":"subscriptions","depth":3},{"value":"Step 2: Initiate Payment","id":"step-2-initiate-payment","depth":3},{"value":"Step 3: Handle Redirect","id":"step-3-handle-redirect","depth":3},{"value":"Step 4: Handle Callback","id":"step-4-handle-callback","depth":3},{"value":"Step 5: Verify Signature","id":"step-5-verify-signature","depth":3},{"value":"Step 6: Handle Webhooks","id":"step-6-handle-webhooks","depth":3},{"value":"Testing","id":"testing","depth":2},{"value":"Test Cards","id":"test-cards","depth":3}],"frontmatter":{"title":"Accept card payments server-to-server","description":"Collect card details on your own PCI-DSS certified surface and charge them through Glomo, server to server.","seo":{"title":"Accept card payments server-to-server"}},"lastModified":"2026-11-06T11:06:03.694Z","pagePropGetterError":{"message":"","name":""}},"slug":"/payin/server-to-server-payments","userData":{"isAuthenticated":false,"teams":["anonymous"]},"isPublic":true}