Linq
Linq is Synqpay's cloud gateway for real-time communication with the device — a single HTTPS call that executes your JSON-RPC request on the real device and hands back its actual response, as if you were calling it directly.
Overview
Every other transport in this section (HTTP, TCP, WebSocket, Serial) requires your client to be on the same local network as the device, or physically connected to it. Linq removes that requirement: your integration calls Linq from anywhere, Linq relays the request to the device over its own persistent connection, waits for the device to actually process it, and hands back the device's real JSON-RPC response synchronously — as if you'd called the device directly.
The JSON-RPC request/response contract itself is unchanged — same method/params/id, same error object shape. What Linq adds is the transport (cloud-reachable HTTPS instead of LAN), OAuth2-based authentication in place of a device-paired api-key, and a small set of its own headers to support that (idempotent retries, an operation id to reference later, and a status describing how the relay itself went).
Endpoints
Linq exposes three endpoints, all on the same host:
| Path | Purpose |
|---|---|
POST /oauth2/token |
Issues an access token. |
POST /synqpay/rpc |
Executes a JSON-RPC request on a device. |
GET /synqpay/rpc/operation |
Returns the current outcome of an operation you already submitted. |
The host isn't published here
Your host is provided to you as part of onboarding — it isn't public and won't appear in this documentation. Everywhere below, <linq-application-host> is a placeholder for the value you were given.
Authorization
Unlike the local transports' device-paired api-key, Linq doesn't use an api-key at all. Authentication is OAuth2, and it authenticates the caller (your integration), not a specific device.
- Log in to your DMS account.
- Create an OAuth Client Application (see Getting Started for the step-by-step walkthrough).
- Exchange that client's id and secret for an access token.
- Send that token as a bearer token on every call to
<linq-application-host>.
Obtaining a token
Body:
| Field | Required | Description |
|---|---|---|
clientId |
Yes | Your OAuth Client Application's id. |
clientSecret |
Yes | Your OAuth Client Application's secret. |
Response — HTTP 200:
expiresIn is the token's lifetime in seconds. Cache the token and reuse it until shortly before it expires rather than requesting one per call.
Token endpoint status codes
Error responses from this endpoint have no body — the status code is the whole answer.
| Status | Meaning |
|---|---|
200 |
Token issued. |
400 |
The request body was malformed, or clientId/clientSecret was missing. |
401 |
The credentials were rejected. |
429 |
Too many requests — back off and retry. A Retry-After header is included when one is available. |
502 |
Linq could not reach the token issuer. Retry after a short backoff. |
504 |
The token issuer did not respond in time. Retry after a short backoff. |
Repeated failures are rate limited
Repeated authentication failures from the same source address are throttled, and may be blocked temporarily. Treat a 401 as a configuration problem to fix, not something to retry in a loop.
Using the token
against <linq-application-host>. With that token, you can reach every device registered in DMS to the account your OAuth Client Application belongs to — access isn't scoped per-device or per-request, it's inherited entirely from the account.
Making a Request
Request Headers
| Header | Required | Description |
|---|---|---|
Authorization |
Yes | Bearer <accessToken> |
Linq-Device-Id |
Yes | Target device identifier. Case-insensitive. |
Idempotency-Key |
Yes | See Idempotency below. Max 128 characters. |
Content-Type |
Yes | application/json |
Device ID format
A device id is <brand>_<serial-number>, all lowercase — for example, ingenico_244rkr528387. Supported brands: ingenico, castles, verifone.
Response Headers
| Header | Description |
|---|---|
Idempotency-Key |
Echoes back the key sent in the request. |
Linq-Operation-Id |
Server-generated id for this operation. Stable across idempotent retries of the same key. |
Linq-Request-Status |
Where this specific request got to — see table below. |
Linq-Request-Status values
Important
Linq-Request-Status describes what happened to the request itself, not whether the operation succeeded. COMPLETED means a response was received from the device — a device-level failure (e.g. MissingParam) still comes back as Linq-Request-Status: COMPLETED, with the actual error object inside the JSON-RPC response body, exactly as the device produced it.
| Value | Meaning |
|---|---|
NOT_DISPATCHED |
The request never reached the device — it failed on Linq's side before it could be sent. If you see this, nothing was attempted against the device at all. |
UNREACHABLE |
Linq sent the request but the device did not respond in time. The outcome is unknown — see 1201 and Fetching an Operation. |
TIMED_OUT |
The device acknowledged the request but never sent a final response within the timeout. It may still be working — see Fetching an Operation. |
COMPLETED |
A final response was received from the device and is returned in the body — check the body's own result/error for the actual outcome. |
ERROR |
The request was dispatched, but Linq failed to relay the outcome (e.g. a malformed response from the device, or an internal relay error) — not a device-level JSON-RPC error. |
Note
If Linq-Request-Status is missing on a response Linq itself produced (an authentication failure, a 413/429 rejection, or Linq shutting down for a deploy), treat it the same as NOT_DISPATCHED — the request never reached this stage, so nothing was attempted against the device.
This does not apply when the response did not come from Linq at all — a 524, or your own client timing out. There you cannot know whether the device acted, so re-send the same request with the same Idempotency-Key and let Linq tell you; see Re-polling a request you lost the answer to.
Fetching an Operation
Returns the current outcome of an operation you already submitted. Use it when a call came back TIMED_OUT or UNREACHABLE, or when you never received its response at all — fetch the operation to verify what the device actually did.
The operation is identified by the Idempotency-Key you sent with the original request, not by a server-generated id, so this works even if you never saw the response headers.
The only header required is Authorization: Bearer <accessToken> — the same token used for POST /synqpay/rpc.
Query Parameters
| Parameter | Required | Description |
|---|---|---|
idempotencyKey |
Yes | The Idempotency-Key sent with the original request. Max 128 characters. |
Responses
| Status | Body | Meaning |
|---|---|---|
200 |
✓ | The operation reached an end state. The body is the device's own JSON-RPC response — byte for byte what a successful POST would have returned. |
202 |
The operation has not reached an end state yet. Fetch again after Retry-After seconds. |
|
400 |
✓ | The operation ended without a device response (1201 or 1202), or idempotencyKey was missing or oversized (1001). |
404 |
✓ | No operation matches that key (1100). |
500 |
Linq could not relay the outcome. |
Response headers match POST /synqpay/rpc: Idempotency-Key, Linq-Operation-Id and Linq-Request-Status. A 202 also carries Retry-After, in seconds — honour it rather than choosing your own interval.
PENDING is only returned here
A 202 reports Linq-Request-Status: PENDING, meaning the operation may still resolve. A POST never returns PENDING. How long an operation can stay in this state is covered in How long an outcome can change.
This endpoint is a read — it never dispatches anything to the device, and fetching an operation any number of times changes nothing.
Idempotency
Idempotency-Key scopes to your account, not to a specific device — the same value must not be reused for two different logical operations.
- Reusing a key with an identical request (same
method,id, andparams) is safe: Linq returns the original result without re-executing anything against the device. - Reusing a key with a different request is rejected as a conflict (see 409 Conflict,
IdempotencyConflictError). - Only a dispatched request registers its
Idempotency-Keyas consumed. A request that comes backNOT_DISPATCHED, or that fails before a request status is even determined (e.g. a disabled device, rejected with403before dispatch is ever considered), never registers the key at all — it stays reusable once you retry. - A
POSTalways returns the outcome its own request produced. Re-sending the same request with the same key returns that same answer — includingTIMED_OUTorUNREACHABLE— even after the device has answered. That is exactly what makes the key safe to retry. To find out whether the outcome has since changed, fetch the operation.
How long a key is remembered
Linq remembers an Idempotency-Key for 24 hours from the original request. Inside that window, re-sending the same request returns the original operation's outcome. After it the key is forgotten, and re-sending becomes a new operation — which, if the original reached the device, means the device executes it a second time.
How long an outcome can change
A TIMED_OUT or UNREACHABLE verdict means Linq stopped waiting, not that the operation ended. If the device's response arrives afterwards, Linq records it against the same operation, and fetching the operation returns the device's real response from then on.
An operation can resolve this way for up to 5 minutes after it was created. Past that, its outcome is fixed and will not change again.
These are two separate clocks, and it's worth keeping them apart:
| Window | Length | What it governs |
|---|---|---|
| Idempotency key retention | 24 hours | How long re-sending the same request returns the original operation instead of starting a new one. |
| Outcome resolution | 5 minutes | How long a late device response can still change what fetching the operation reports. |
Re-polling a request you lost the answer to
If a call is dispatched but you never receive its response — the connection drops, your client times out, a proxy in front of you gives up — you have two ways to recover, and they answer different questions.
To get the original request's answer, repeat the exact same request with the same Idempotency-Key. Linq returns the original operation's outcome rather than executing anything again, and if that operation is still running it waits for it to finish and returns the real result.
To find out what the device actually did, fetch the operation. Unlike a re-sent POST, that reflects a response which arrived after Linq had already stopped waiting.
A new key is a new operation
Retrying with a new Idempotency-Key will attempt to execute the operation on the device again. Re-use the same key to avoid charging the cardholder twice.
Timing
A call to /synqpay/rpc blocks synchronously until one of the following happens:
- If Linq hears nothing at all from the device within 2 seconds, the call returns
Linq-Request-Status: UNREACHABLE. - If the device acknowledges but no final response arrives within 2 minutes, the call returns
Linq-Request-Status: TIMED_OUT.
There is no separate polling step — the single HTTP request is held open for the full round trip.
GET /synqpay/rpc/operation does not block. It returns the operation's state immediately, and a 202 tells you when to fetch again. An outcome can still change for 5 minutes after the operation was created; see How long an outcome can change.
Hebrew / Arabic support
The Synqpay API supports UTF-8 encoding for all characters, including Hebrew and Arabic. It is highly recommended that each request include the header Content-Type: application/json; charset=utf-8.
CORS
Linq is a server-to-server integration, and CORS is not enabled. A browser calling <linq-application-host> directly will be blocked by the browser's same-origin policy. This is deliberate — your OAuth client credentials should never be exposed to a browser context.
Status Codes and Errors
Unlike the local transports (which always return HTTP 200 for any JSON-RPC call, success or device-level error), Linq uses real HTTP status codes for problems at the relay layer — authentication, validation, rate limiting, and so on. A device-level JSON-RPC error still comes back as HTTP 200, with the error inside the response body.
| Status | Body | Description |
|---|---|---|
200 |
✓ | Relay succeeded — body is the device's own JSON-RPC response (result or error). |
202 |
The operation has not reached an end state yet (GET /synqpay/rpc/operation only). |
|
400 |
✓ | Bad request — malformed JSON-RPC envelope, missing/invalid headers, unknown device, or the device was unreachable/timed out. |
401 |
Authentication failed. | |
403 |
✓ | Access denied — you're not authorized for this device, or it's disabled. |
404 |
✓ | No operation matches the idempotency key you asked about. |
409 |
✓ | Idempotency key conflict. |
413 |
Request body too large. | |
429 |
Too many requests from this client. | |
500 |
Internal error. | |
503 |
Service Unavailable. Retry after a short backoff. | |
524 |
Not returned by Linq — a proxy gave up waiting. Shouldn't happen; see below. |
Statuses with a body return the same JSON-RPC error object shape as device errors:
The code's numeric range tells you the source: Linq's own codes are always 4 digits (1000+) or a negative JSON-RPC reserved code; device application errors are always in the 100–499 range.
400 Bad Request
| Code | Name | Description |
|---|---|---|
-32700 |
ParseError |
The request body isn't valid JSON, or the JSON-RPC envelope is structurally invalid (a required field like jsonrpc, method, or id is missing or malformed). |
1001 |
InvalidInputError |
A request header, field or query parameter failed validation (e.g. a missing or oversized Linq-Device-Id, Idempotency-Key or idempotencyKey). |
1102 |
DeviceNotFoundError |
The Linq-Device-Id header doesn't match a device known to your account. |
1103 |
DeviceOfflineError |
The device is not connected, so the request was not sent. Nothing was attempted against the device and your Idempotency-Key is unused — retry when the device is back online. |
1201 |
DeviceUnreachableError |
Linq sent the request but the device did not respond in time. On rare occasions this is a false negative: the device received the request and acted on it, and only its response was lost — a brief network failure between the device and Linq is enough to cause it. Treat the outcome as unknown, fetch the operation to learn what the device did, and do not retry with a new Idempotency-Key. |
1202 |
DeviceTimeoutError |
The device acknowledged the request but didn't send a final response within 2 minutes. It is most likely still working — fetch the operation to see whether it finished. |
401 Unauthorized
No error body is returned for authentication failures. Every cause — missing/malformed Authorization header, expired or invalid token, a disabled or unknown account — produces an identical, bodyless 401, deliberately, so a caller can't distinguish "wrong token" from "unknown account." Confirm your bearer token is present and unexpired, and request a new one from /oauth2/token if needed.
403 Forbidden
| Code | Name | Description |
|---|---|---|
1101 |
AccessDeniedError |
Either your credentials aren't authorized for this device, or the device is disabled. |
404 Not Found
| Code | Name | Description |
|---|---|---|
1100 |
NotFoundError |
No operation matches the idempotencyKey given to GET /synqpay/rpc/operation — either the key was never used, or the operation has passed its 24-hour retention window. |
409 Conflict
| Code | Name | Description |
|---|---|---|
1300 |
IdempotencyConflictError |
The Idempotency-Key was reused with a different request (different method, id, or params) — see Idempotency. |
500 Internal Server Error
| Code | Name | Description |
|---|---|---|
1000 |
InternalError |
An unexpected internal error occurred on Linq's side. Retry; if it persists, contact support with the request's approximate time and Linq-Operation-Id (if one was returned). |
524
A 524 does not come from Linq — it means a proxy between you and Linq stopped waiting for the response. Linq's own deadline is deliberately shorter than that proxy's, so a slow device returns Linq-Request-Status: TIMED_OUT well before a 524 becomes possible. You shouldn't see one.
If you do, the request may still have reached the device, so treat it as a lost response rather than a failure: fetch the operation using the same Idempotency-Key to find out what actually happened (see Re-polling a request you lost the answer to). Then report it to support with the approximate time of the call — a 524 indicates something is wrong on our side, not yours.
Postman
Test /synqpay/rpc calls against your device without writing any code.
Before you start
You'll need:
- Your Linq host (
<linq-application-host>) and your OAuth Client Application's client id and secret — see Getting Started if you don't have these yet. - A device id registered to your account.
- Postman installed, or the web version.
Get the collection
Click Run in Postman to fork the collection into your own workspace.
Forking also prompts you to create a Postman Environment — this is where the collection's variables live.
Warning
Creating the environment doesn't select it. Use the environment dropdown in Postman's top-right corner to make it active — otherwise {{variables}} won't resolve and every request will fail.
Set up your environment
Fill in the following variables in your environment:
| Variable | Value |
|---|---|
linq-application-host |
Your Linq host |
linq-client-id |
Your OAuth Client Application's id |
linq-client-secret |
Your OAuth Client Application's secret |
device-id |
The device you want to call |
Leave bearer-token empty — it's filled in automatically in the next step.
Get a bearer token
Open the Linq Authorization folder and run the token request. On a successful response, a script automatically saves the returned accessToken into your bearer-token variable. Every request in the Synqpay API folder inherits the collection's Bearer auth from that same variable, so this is the only manual step — repeat it whenever your token expires.
Make your first call
Open the Synqpay API folder and send any request — for example, getDeviceInfo. Linq-Device-Id is filled from your device-id variable, and a fresh Idempotency-Key is generated automatically on every send — there's nothing else to configure per request.