Cancel / Reverse POS Charge
Cancels (voids) a POS charge before it has been settled. Both the /cancel and /reverse paths invoke the same operation — use whichever naming convention fits your integration. If the charge has already been settled, use the Refund endpoint instead.
MSK encryption: Response body is MSK-encrypted (TR31-DATA). No request body (HMAC signs empty body). See End-to-End Example.
Purpose
- Void a charge that has not yet been settled with the acquirer
- Cancel an accidental or incorrect charge immediately
- The
/reversealias is provided for integrations that use reversal terminology
Cancel / Reverse voids the authorization before settlement — the cardholder is never charged. Refund returns funds after settlement has occurred. Use cancel when the transaction is still pending; use refund after the charge has settled.
Authentication
POS HMAC Signature — requires device identification headers and HMAC signature for every request.
Device Identification Headers
| Header | Required | Format | Description |
|---|---|---|---|
X-Terminal-Id | Yes | Terminal ID string | Identifier of the terminal requesting the cancellation. Example: TERM-001 |
User-Agent | Yes | Viopay-POS/{version} ({OS}; {model}) | POS app version string. Must start with Viopay-POS/. Example: Viopay-POS/1.4.2 (Android 12; PAX-A920) |
X-Client-Type | Yes | pos | Client type identifier. Always pos for POS devices. |
Signature Headers
| Header | Required | Format | Description |
|---|---|---|---|
X-Key-Id | Yes | Free-form string | Identifier of the HMAC key used to generate the signature. Example: hmac-key-2025-01 |
X-Timestamp | Yes | RFC3339 | Current time in UTC. Must be within 5 minutes of server time. Example: 2025-12-13T14:25:30Z |
X-Nonce | Yes | Alphanumeric string | Unique random value for each request. At least 12 characters. Example: a1b2c3d4e5f6 |
X-Signature | Yes | v1={base64_hmac} | HMAC-SHA256 signature over the request. See Signature Computation. |
See the Headers Reference for complete details on all POS headers.
Request
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
chargeID | string | Yes | The charge ID to cancel (returned from Create POS Charge) |
Body
No request body is required. The cancellation is identified entirely by the charge ID in the path.
Response
200 OK — MSK-encrypted envelope
Decrypt with MSK to obtain:
{
"charge_id": "ch_abc123def456",
"status": "cancelled"
}
Wire format:
{
"format": "TR31-DATA",
"ciphertext": "D4E5F6...",
"initialization_vector": "101112131415161718191A1B1C1D1E1F",
"mode": "CBC",
"key_kcv": "ABCD12"
}
| Field | Type | Description |
|---|---|---|
charge_id | string | The charge that was cancelled |
status | string | Always "cancelled" on success |
400 Bad Request
Returned when the charge ID is missing or the charge is not in a cancellable state.
{
"error": {
"code": "4000",
"message": "Charge cannot be cancelled",
"trace_id": "abc123..."
}
}
401 Unauthorized
Returned when HMAC signature validation fails.
{
"error": {
"code": "4001",
"message": "Invalid signature",
"trace_id": "abc123..."
}
}
500 Internal Server Error
Returned when the payment gateway returns an error or processing fails.
{
"error": {
"code": "5000",
"message": "Gateway processing error",
"trace_id": "abc123..."
}
}
Code Examples
- cURL
- Python
- Ruby
- Go
- C#
- PHP
- Java
curl -X POST https://api.viopay.io/pos/charge/ch_abc123def456/cancel \
-H "X-Terminal-Id: TERM-001" \
-H "User-Agent: Viopay-POS/1.4.2 (Android 12; PAX-A920)" \
-H "X-Client-Type: pos" \
-H "X-Key-Id: hmac-key-2025-01" \
-H "X-Timestamp: 2025-12-13T14:25:30Z" \
-H "X-Nonce: a1b2c3d4e5f6" \
-H "X-Signature: v1=SGVsbG8gV29ybGQ=" \
-H "Content-Type: application/json"
import requests
import hashlib
import hmac
import base64
import uuid
from datetime import datetime, timezone
terminal_id = "TERM-001"
key_id = "hmac-key-2025-01"
hmac_secret = b"your-hmac-secret"
timestamp = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
nonce = uuid.uuid4().hex[:12]
charge_id = "ch_abc123def456"
path = f"/pos/charge/{charge_id}/cancel"
signature_string = f"POST\n{path}\n\n\n{terminal_id}\n{timestamp}\n{nonce}"
signature = hmac.new(hmac_secret, signature_string.encode(), hashlib.sha256).digest()
signature_b64 = base64.b64encode(signature).decode()
headers = {
"X-Terminal-Id": terminal_id,
"User-Agent": "Viopay-POS/1.4.2 (Android 12; PAX-A920)",
"X-Client-Type": "pos",
"X-Key-Id": key_id,
"X-Timestamp": timestamp,
"X-Nonce": nonce,
"X-Signature": f"v1={signature_b64}",
"Content-Type": "application/json",
}
response = requests.post(
f"https://api.viopay.io/pos/charge/{charge_id}/cancel",
headers=headers,
)
data = response.json()
print(f"Charge ID: {data['charge_id']}")
print(f"Status: {data['status']}")
require 'net/http'
require 'json'
require 'uri'
require 'openssl'
require 'base64'
require 'securerandom'
require 'time'
terminal_id = "TERM-001"
key_id = "hmac-key-2025-01"
hmac_secret = "your-hmac-secret"
timestamp = Time.now.utc.strftime("%Y-%m-%dT%H:%M:%SZ")
nonce = SecureRandom.hex(6)
charge_id = "ch_abc123def456"
path = "/pos/charge/#{charge_id}/cancel"
signature_string = "POST\n#{path}\n\n\n#{terminal_id}\n#{timestamp}\n#{nonce}"
signature = Base64.strict_encode64(
OpenSSL::HMAC.digest("SHA256", hmac_secret, signature_string)
)
uri = URI("https://api.viopay.io/pos/charge/#{charge_id}/cancel")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post.new(uri)
request["X-Terminal-Id"] = terminal_id
request["User-Agent"] = "Viopay-POS/1.4.2 (Android 12; PAX-A920)"
request["X-Client-Type"] = "pos"
request["X-Key-Id"] = key_id
request["X-Timestamp"] = timestamp
request["X-Nonce"] = nonce
request["X-Signature"] = "v1=#{signature}"
request["Content-Type"] = "application/json"
response = http.request(request)
data = JSON.parse(response.body)
puts "Charge ID: #{data['charge_id']}"
puts "Status: #{data['status']}"
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"net/http"
"time"
"github.com/google/uuid"
)
type CancelResponse struct {
ChargeID string `json:"charge_id"`
Status string `json:"status"`
}
func main() {
terminalID := "TERM-001"
keyID := "hmac-key-2025-01"
hmacSecret := []byte("your-hmac-secret")
timestamp := time.Now().UTC().Format(time.RFC3339)
nonce := uuid.New().String()[:12]
chargeID := "ch_abc123def456"
path := fmt.Sprintf("/pos/charge/%s/cancel", chargeID)
sigString := fmt.Sprintf("POST\n%s\n\n\n%s\n%s\n%s", path, terminalID, timestamp, nonce)
mac := hmac.New(sha256.New, hmacSecret)
mac.Write([]byte(sigString))
signature := base64.StdEncoding.EncodeToString(mac.Sum(nil))
url := fmt.Sprintf("https://api.viopay.io/pos/charge/%s/cancel", chargeID)
req, _ := http.NewRequest("POST", url, nil)
req.Header.Set("X-Terminal-Id", terminalID)
req.Header.Set("User-Agent", "Viopay-POS/1.4.2 (Android 12; PAX-A920)")
req.Header.Set("X-Client-Type", "pos")
req.Header.Set("X-Key-Id", keyID)
req.Header.Set("X-Timestamp", timestamp)
req.Header.Set("X-Nonce", nonce)
req.Header.Set("X-Signature", fmt.Sprintf("v1=%s", signature))
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
var result CancelResponse
json.Unmarshal(body, &result)
fmt.Printf("Charge ID: %s\n", result.ChargeID)
fmt.Printf("Status: %s\n", result.Status)
}
using System.Net.Http;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
var terminalId = "TERM-001";
var keyId = "hmac-key-2025-01";
var hmacSecret = Encoding.UTF8.GetBytes("your-hmac-secret");
var timestamp = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ssZ");
var nonce = Guid.NewGuid().ToString("N")[..12];
var chargeId = "ch_abc123def456";
var path = $"/pos/charge/{chargeId}/cancel";
var sigString = $"POST\n{path}\n\n\n{terminalId}\n{timestamp}\n{nonce}";
var signature = Convert.ToBase64String(
new HMACSHA256(hmacSecret).ComputeHash(Encoding.UTF8.GetBytes(sigString))
);
var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, $"https://api.viopay.io/pos/charge/{chargeId}/cancel");
request.Headers.Add("X-Terminal-Id", terminalId);
request.Headers.Add("User-Agent", "Viopay-POS/1.4.2 (Android 12; PAX-A920)");
request.Headers.Add("X-Client-Type", "pos");
request.Headers.Add("X-Key-Id", keyId);
request.Headers.Add("X-Timestamp", timestamp);
request.Headers.Add("X-Nonce", nonce);
request.Headers.Add("X-Signature", $"v1={signature}");
var response = await client.SendAsync(request);
var responseBody = await response.Content.ReadAsStringAsync();
var data = JsonSerializer.Deserialize<JsonElement>(responseBody);
Console.WriteLine($"Charge ID: {data.GetProperty("charge_id")}");
Console.WriteLine($"Status: {data.GetProperty("status")}");
<?php
$terminalId = "TERM-001";
$keyId = "hmac-key-2025-01";
$hmacSecret = "your-hmac-secret";
$timestamp = gmdate("Y-m-d\TH:i:s\Z");
$nonce = bin2hex(random_bytes(6));
$chargeId = "ch_abc123def456";
$path = "/pos/charge/{$chargeId}/cancel";
$sigString = "POST\n{$path}\n\n\n{$terminalId}\n{$timestamp}\n{$nonce}";
$signature = base64_encode(hash_hmac("sha256", $sigString, $hmacSecret, true));
$ch = curl_init("https://api.viopay.io/pos/charge/{$chargeId}/cancel");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"X-Terminal-Id: {$terminalId}",
"User-Agent: Viopay-POS/1.4.2 (Android 12; PAX-A920)",
"X-Client-Type: pos",
"X-Key-Id: {$keyId}",
"X-Timestamp: {$timestamp}",
"X-Nonce: {$nonce}",
"X-Signature: v1={$signature}",
"Content-Type: application/json",
],
]);
$response = curl_exec($ch);
curl_close($ch);
$data = json_decode($response, true);
echo "Charge ID: " . $data["charge_id"] . "\n";
echo "Status: " . $data["status"] . "\n";
import java.net.http.*;
import java.net.URI;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
import java.util.UUID;
import java.time.Instant;
String terminalId = "TERM-001";
String keyId = "hmac-key-2025-01";
byte[] hmacSecret = "your-hmac-secret".getBytes();
String timestamp = Instant.now().toString();
String nonce = UUID.randomUUID().toString().replace("-", "").substring(0, 12);
String chargeId = "ch_abc123def456";
String path = "/pos/charge/" + chargeId + "/cancel";
String sigString = "POST\n" + path + "\n\n\n" + terminalId + "\n" + timestamp + "\n" + nonce;
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(hmacSecret, "HmacSHA256"));
String signature = Base64.getEncoder().encodeToString(mac.doFinal(sigString.getBytes()));
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.viopay.io/pos/charge/" + chargeId + "/cancel"))
.POST(HttpRequest.BodyPublishers.noBody())
.header("X-Terminal-Id", terminalId)
.header("User-Agent", "Viopay-POS/1.4.2 (Android 12; PAX-A920)")
.header("X-Client-Type", "pos")
.header("X-Key-Id", keyId)
.header("X-Timestamp", timestamp)
.header("X-Nonce", nonce)
.header("X-Signature", "v1=" + signature)
.header("Content-Type", "application/json")
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
What's Next?
- Need to return funds after settlement? → Refund POS Charge
- Create a new charge → Create POS Charge