Accept card payments with GetPay payment links
Create a payment link from your server, send your customer to it, and get notified when they pay. GetPay hosts the payment page, talks to the bank, and reports every result back to you.
01How it works
A payment link is a hosted payment page for one order. Your server creates it with your order details, and GetPay takes care of the rest.
- Your serverCreates a payment link with the items, amount and your order number. GetPay returns a
url. - Your websiteRedirects the customer to that
url. - CustomerReviews the order, enters their details and pays by card on the bank’s secure page. If the card is declined, they can try again on the same link.
- GetPayChecks the result with the bank, sends the customer to your
success_url, and sends apayment_link.completedwebhook to your server. - Your serverConfirms the payment and fulfils the order.
sequenceDiagram
participant S as Your server
participant C as Customer
participant G as GetPay
participant B as Bank (vPOS)
S->>G: POST /payment-links
G-->>S: { id, url }
S->>C: Redirect to url
C->>G: Opens payment page, presses Pay
G->>B: Register payment
C->>B: Enters card, 3-D Secure
B->>G: Result
G->>C: Redirect to success_url
G->>S: Webhook payment_link.completed
S->>G: GET /payment-links/{id} (confirm)
02Before you start
Your domain and base URL
The API runs on your own domain, not on getpay.am. In the GetPay dashboard open the Domain tab, enter your domain (for example shopx.am) and add the four DNS records it shows, all pointing to the GetPay IP address:
| Type | Name | Used for |
|---|---|---|
| A | api | Your API base URL: https://api.shopx.am/api/v1 |
| A | merchant | Your dashboard |
| A | checkout | Payment pages your customers pay on |
| A | receipt | Receipt pages |
Then press Check connection. Once all records are found, the dashboard shows your API base URL. Requests sent to any other address, including api.getpay.am, are refused with 403.
API keys
Find your keys in the GetPay dashboard under Home → API keys. You have two, one per environment:
| Key | Use it for |
|---|---|
| sk_sandbox_… | Building and testing. Payments end on the GetPay test page; no bank is contacted and no money moves. |
| sk_live_… | Real payments. Works after your merchant account goes live and a bank is connected. |
Send the key in the Authorization header of every request:
GETPAY_SECRET_KEY.Conventions
- Amounts are in minor units.
150000means 1,500.00 AMD. Always send whole numbers. - Currency: the currencies GetPay has enabled —
GET /meta/currencies(AMDfor now). Others returnCURRENCY_NOT_SUPPORTED. - Requests and responses are JSON. Send
Content-Type: application/json. - Times are UTC in ISO 8601, for example
2026-09-30T10:15:00Z. - IDs are UUIDs. Store the payment link
idnext to your order.
03Create a payment link
Call this from your server when the customer is ready to pay. Send what they are buying, who they are (if you know), and where to send them afterwards.
Parameters
Send either line_items or amount, not both. Everything else is optional.
| Field | Required | Description |
|---|---|---|
| line_items[] | One of the two | Products the customer pays for, up to 100. The total is calculated from them. |
| line_items[].name | Yes | Product name shown on the payment page. Up to 255 characters. |
| line_items[].unit_amount | Yes | Price per unit in minor units. |
| line_items[].quantity | No | 1 to 10,000. Default 1. |
| line_items[].description | No | Short text under the name. Up to 1,000 characters. |
| line_items[].image_url | No | Public http(s) image URL shown next to the item. |
| line_items[].product_id | No | Your SKU or product ID. |
| line_items[].metadata | No | Key/value strings for your own use. |
| line_items[].ehdm | No | Fiscal receipt data. Required while EHDM is enabled, see Fiscal receipts. |
| amount | One of the two | Fixed total in minor units when you don’t send items. Requires title or description. |
| currency | No | AMD (default), or another currency enabled by GetPay (see GET /meta/currencies). |
| title | No | Headline on the payment page, e.g. “Order #1001”. |
| description | No | Longer text on the page. Up to 2,000 characters. |
| merchant_reference | No | Your order number. Returned on the link, its payments and every webhook, and searchable. |
| language | No | Page language: hy (default), en or ru. The customer can switch it. |
| customer.email | No | Pre-fills the payment page. |
| customer.name | No | Pre-fills the payment page. |
| customer.phone | No | Pre-fills the payment page. |
| customer.external_id | No | Your customer ID. Never shown to the customer. |
| customer.address | No | line1, line2, city, region, postal_code, country (2-letter code, e.g. AM). |
| customer_fields | No | What the page asks for. For each of email, name, phone, address: required, optional or hidden. Default: email required, name and phone optional, address hidden. |
| success_url | No | Where the customer goes after paying. GetPay adds payment_link_id, payment_id, receipt_number and receipt_url to it (see Receipts). |
| cancel_url | No | “Back to store” link on the payment page. |
| payment_methods | No | Limit the methods offered, e.g. ["ameriabank"]. Default: all you have enabled. |
| expires_in_minutes | No | 30 to 43,200 (30 days). Default 1,440 (24 hours). |
| metadata | No | Up to 50 keys. Keys up to 40 characters, values up to 500. Returned on the link, its payments and webhooks. |
| ehdm | No | Fiscal receipt options: skip, skip_reason, item. See Fiscal receipts. |
Response
201 Created with the payment link. Save id with your order and send the customer to url.
GET /payment-links?merchant_reference=order-1001.04Send the customer to the payment page
Redirect the customer’s browser to the url from the response. The page shows your business name, the items and total, asks for the fields you chose, and offers card payment. The customer can switch between Armenian, English and Russian.
What happens next
| Outcome | Where the customer ends up |
|---|---|
| paid | Your success_url with ?payment_link_id=…&payment_id=…&receipt_number=…&receipt_url=… added. Without a success_url, a GetPay confirmation page with a link to the receipt. |
| declined | Back on the payment page with the reason (for example “Not enough money on the card”). They can try again or use another card until the link expires. |
| clicks back | Your cancel_url. The link stays open, so they can come back to it. |
05Confirm the payment
Use either way, or both. Webhooks are the reliable way because they arrive even if the customer closes the browser before reaching your success page.
- Webhook: handle
payment_link.completed(see Webhooks). - API: when the customer lands on your
success_url, fetch the link.
Fulfil the order only when status is complete and payment_status is paid, and the amount_total and currency match your order. This call also re-checks a pending payment with the bank.
Payment link statuses
| status | Meaning |
|---|---|
| open | Waiting for payment. Declined attempts leave it open. |
| complete | Paid. payment_status is paid and payment holds the successful payment. |
| expired | expires_at passed before payment. |
| canceled | You closed it with POST /payment-links/{id}/expire. |
Payment statuses
Each attempt to pay is a payment, returned in the link’s payment field and by the payments API.
| status | Meaning |
|---|---|
| pending | The customer is paying, or the bank has not confirmed yet. |
| succeeded | Money was taken from the card. |
| failed | Declined. failure_reason says why. The customer may retry. |
| refunded | Fully refunded. |
| partially_refunded | Part of the amount was refunded. See amount_refunded. |
| canceled | Abandoned, for example the link was canceled mid-payment. |
| reversed | You reversed the whole payment at the bank within 72 hours. See reversed_at. |
06Webhooks
GetPay sends an HTTPS POST to your server when something happens, so you don’t have to poll. Each request is signed with a secret only you and GetPay know.
Set up an endpoint
- In the dashboard, open Webhooks, choose Sandbox or Live, and add your URL, e.g.
https://your-site.am/webhooks/getpay. - Choose all events or only the ones you need.
- Copy the signing secret (
whsec_…) and store it on your server asGETPAY_WEBHOOK_SECRET. It is different from your API key. - Press Send test to check your endpoint. The dashboard shows every delivery with your server’s response.
Live endpoints must use https://. Sandbox endpoints may use http://. GetPay cannot reach localhost; to receive events on your computer while developing, use a tunnel such as ngrok http 8001 and register the public URL it gives you.
Events
| Event | When | data.object |
|---|---|---|
| payment_link.completed | The link was paid. Fulfil the order on this event. | payment link |
| payment.succeeded | A card payment was approved. | payment |
| payment.failed | A payment attempt was declined. The customer may still retry and pay. | payment |
| payment.refunded | A payment was fully or partially refunded. | payment |
| payment.reversed | A whole payment was reversed at the bank. | payment |
| receipt.created | A receipt was issued because a payment was paid. Send url to your customer. | receipt |
| receipt.updated | A receipt changed after a refund or reversal. | receipt |
| payment_link.canceled | You closed the link through the API. | payment link |
| webhook.test | You pressed Send test in the dashboard. | test message |
Request format
| Header | Value |
|---|---|
| GetPay-Signature | t=1790755839,v1=5f2b…: the send time and an HMAC-SHA256 signature. |
| GetPay-Event-Id | Unique event ID. Use it to ignore duplicates. |
| GetPay-Event-Type | For example payment_link.completed. |
| Content-Type | application/json |
data.object has exactly the same shape as the API response for that object.
Verify the signature
The signature is HMAC-SHA256(secret, t + "." + raw_body) in hex. Compute it over the raw request body (re-encoded JSON will not match), compare in constant time, and reject requests older than 5 minutes.
Respond and retry
- Return any 2xx within 10 seconds. Do slow work (emails, stock updates, ERP sync) in a background job after responding.
- Anything else is retried after 1 minute, 5 minutes, 30 minutes, 2 hours, 6 hours, 12 hours and 24 hours. After that the delivery is marked failed, and you can resend it from the dashboard.
- Expect duplicates. The same event can arrive more than once. Store processed
GetPay-Event-Idvalues and skip repeats. - Don’t rely on order. Events can arrive out of order. Use the status inside
data.object, or fetch the latest from the API. - Double-check if unsure. Fetch the event with
GET /events/{id}using your secret key.
07Test in sandbox
With your sk_sandbox_… key, everything works like live, but pressing Pay opens the GetPay test page instead of a bank. You choose how the payment ends. Webhooks, redirects, decline messages and refunds behave exactly as they will in live mode. No bank credentials are needed.
| Test page button | Result | Bank code |
|---|---|---|
| Succeed payment | succeeded with test card 424242******4242 | 00 |
| Not enough money | failed | 0116 |
| Expired card | failed | 0101 |
| Declined by card issuer | failed | 0100 |
| 3-D Secure failed | failed | 0-2006 |
| Leave processing | pending: the bank has not confirmed yet; finish it later from the test page | — |
Test these before going live: a successful payment, a decline followed by a successful retry, a customer who closes the browser after paying (your webhook must still fulfil the order), and a refund.
08Refunds and reversals
There are two ways to give money back. Both return it to the customer’s card through the bank.
| Action | Amount | When | Result |
|---|---|---|---|
| Reverse | Always the whole payment | Within 72 hours of the payment starting, and only if nothing was refunded yet | reversed |
| Full refund | Everything not yet refunded | Any time after a successful payment | refunded |
| Partial refund | Any amount up to what remains; repeatable | Any time after a successful payment | partially_refunded, then refunded when nothing remains |
Every payment tells you what is possible right now, so you don’t have to track the rules yourself:
| Field | Meaning |
|---|---|
| can_reverse | true while a reversal is possible. |
| reversible_until | The reversal deadline: 72 hours after the payment started. Show it to your staff. |
| can_refund | true while something can still be refunded. |
| amount_refundable | The most you can still refund, in minor units. |
| amount_refunded | Refunded so far. |
| reversed_at | When the payment was reversed. |
Reverse a payment
Cancels the whole payment at the bank. Use it when an order is canceled shortly after payment. After 72 hours, or once anything has been refunded, the request returns 409 and you need a refund instead. A payment.reversed webhook follows.
Refund a payment
Send {"amount": 500000} for a partial refund of up to amount_refundable. Send an empty body or {} for a full refund of everything that remains. You can make several partial refunds. A payment.refunded webhook follows each one.
Both requests return the updated payment. The payment ID is in the link’s payment.id field and in the payment_id added to your success URL.
09Receipts
Every paid payment gets a receipt, much like Stripe receipts: a short number the customer can quote, such as 4821-0937, and a receipt page hosted by GetPay on your domain at https://receipt.your-domain.am/…. The page shows your business details, the items, the card used, any refunds, and a button to save it as a PDF. It is available in Armenian, English and Russian.
The receipt is issued the moment the payment is paid, so the link is already there when the customer returns to your site and when the webhooks arrive. There is nothing to create or enable.
Where you get the receipt link
| Channel | What you get |
|---|---|
| Success URL | receipt_number and receipt_url are added to your success_url, next to payment_link_id and payment_id. Use them to show a “View receipt” link on your thank-you page. |
| Webhooks | receipt.created when the payment is paid and receipt.updated after each refund or reversal. data.object is the full receipt. Every payment.* and payment_link.* event also carries receipt_number and receipt_url on the payment. |
| API | receipt_number and receipt_url on every payment (GET /payments/{id} and the link’s payment), and the full receipt at GET /payments/{id}/receipt. |
receipt_url from the webhook or the API.Receipt object
| Field | Meaning |
|---|---|
| number | Receipt number shown to the customer. Unique within your account. |
| url | Public receipt page. Same value as receipt_url on the payment. |
| status | The payment status: succeeded, partially_refunded, refunded or reversed. |
| amount | Amount paid, in minor units. |
| amount_refunded | Refunded so far. Each refund is listed in refunds[] with its date. |
| amount_net | What the customer finally paid: amount − amount_refunded, or 0 after a reversal. |
| line_items[] | The items from the payment link. |
| merchant | Your business name, legal name, tax ID (ՀՎՀՀ), address and support contacts, taken from your dashboard profile. |
| customer | Email, name and phone the customer entered. |
| payment_method | Bank, masked card number, cardholder name, approval code and RRN. |
| paid_at, issued_at | When the payment was made and when the receipt was issued. |
10Fiscal receipts (EHDM)
If your business must issue cash register receipts, GetPay can register every successful payment with the State Revenue Committee through the PayX electronic cash register (Է-ՀԴՄ, ehdm.am). You need your own contract with PayX. Connect the API login they give you in the dashboard under Settings → EHDM.
While EHDM is enabled, every payment link must say what is sold: the classifier code, the name in Armenian and the unit. GetPay issues the fiscal receipt automatically once the payment succeeds, and the return receipt after a reversal or a full refund.
Check whether EHDM is enabled
enabled: true means your payment links must carry fiscal data (or skip it). dep is the tax department you chose in your EHDM settings; it is used for line items that send no dep.
Fiscal data on a payment link
| Field | Required | Description |
|---|---|---|
| line_items[].ehdm.adg_code | Yes* | Classifier code: ԱԴԳՏ for services (e.g. 85.59 — courses) or ԱՏԳ ԱԱ for goods (e.g. 5401). Search with GET /ehdm/adg-codes?q=…. |
| line_items[].ehdm.good_name | Yes* | Name in Armenian as printed on the receipt, up to 50 characters. Defaults to line_items[].name when it fits. |
| line_items[].ehdm.unit | Yes* | One of: հատ, կգ, գրամ, լիտր, մետր, ք/մ, խ/մ, մլ, այլ — piece, kg, gram, liter, meter, m², m³, ml, other. Printed on the receipt as sent. |
| line_items[].ehdm.good_code | Yes* | Internal code — your own product code or barcode (up to 50 characters), printed on the receipt as Ն/Կ. |
| line_items[].ehdm.dep | No | Tax department: 1 VAT, 2 VAT-exempt, 3 turnover tax, 7 micro-enterprise. Defaults to the tax department in your EHDM settings. |
| ehdm.item | No | The same fields for an amount link without line items. good_name defaults to title. |
| ehdm.skip | No | true creates the link without a fiscal receipt, e.g. when it is issued elsewhere. ehdm.skip_reason is an optional note. |
EHDM_FIELDS_REQUIRED with the list of missing fields.After the payment
The receipt is registered in the background, usually within seconds. The customer’s receipt page (receipt_url) shows it like a cash register receipt, with the fiscal number, QR code and a link to the PayX PDF. You get it in three places:
| Channel | What you get |
|---|---|
| API | fiscal_receipt on every payment, and GET /payments/{id}/fiscal-receipt. |
| Webhooks | receipt.updated when the fiscal receipt is issued and when it is returned. data.object.fiscal holds it. |
| Dashboard | Transaction details show the fiscal receipt, its status and a Retry button. |
Fiscal receipt statuses
| Status | Meaning |
|---|---|
pending | Waiting to be registered (or retrying). |
issued | Registered with the tax authority. |
failed | Could not be registered after all retries. Retry it from the dashboard or the API. |
reverse_pending | Return receipt is being issued after a reversal or full refund. |
reversed | Return receipt issued. |
reverse_failed | Return receipt could not be issued. Retry it. |
canceled | The payment was returned before the receipt was registered, so nothing was registered. |
failed; retry with POST /payments/{id}/fiscal-receipt/retry. A partial refund can’t be returned per product automatically: needs_manual_return becomes true and you issue the return receipt in PayX.livemode: false and never reach PayX or the tax authority, so you can test the whole flow, including validation errors.11API reference
All endpoints use your secret key. A sandbox key only sees sandbox data, and a live key only sees live data.
| Endpoint | Description |
|---|---|
| POST /payment-links | Create a payment link. |
| GET /payment-links | List links, newest first.Filters: status, merchant_reference, customer_external_id, limit (1–100, default 20), offset. |
| GET /payment-links/{id} | Retrieve a link and its latest payment. |
| POST /payment-links/{id}/expire | Close an unpaid link so it can no longer be paid. |
| GET /payments | List payments.Filters: status, merchant_reference, payment_link_id, limit, offset. |
| GET /payments/{id} | Retrieve a payment, including card details (masked number, holder name, approval code, RRN). |
| GET /payments/{id}/receipt | Retrieve the receipt of a paid payment (404 until it is paid). Same object as in receipt.* webhooks. |
| POST /payments/{id}/refund | Refund all (no amount) or part (amount) of a payment. |
| POST /payments/{id}/reverse | Reverse the whole payment within 72 hours. |
| GET /meta/currencies | Currencies GetPay knows; payment links can be created in those with is_enabled: true. No key needed. |
| GET /ehdm | Whether EHDM is enabled for your account, and your tax department. |
| GET /ehdm/adg-codes | Search classifier codes.Filters: q (code prefix or part of the name), kind (adgt or atg), limit. |
| GET /payments/{id}/fiscal-receipt | Fiscal receipt of a payment. |
| POST /payments/{id}/fiscal-receipt/retry | Retry a failed or reverse_failed fiscal receipt. |
| GET /events | List events, newest first.Filters: type, limit, offset. |
| GET /events/{id} | Retrieve one event, for example to confirm a webhook you received. |
Lists return { "object": "list", "items": [...], "total", "limit", "offset" }.
12Errors
Errors return a JSON body with the HTTP status, a stable message_code to check in your code, and a readable message.
| Status | message_code | Meaning |
|---|---|---|
| 400 | BAD_REQUEST | The request can’t be processed, e.g. an unsupported currency or no payment method enabled. Read message. |
| 400 | EHDM_FIELDS_REQUIRED | EHDM is enabled and fiscal data is missing. details.missing lists the fields, e.g. line_items[0].ehdm.adg_code. |
| 400 | CURRENCY_NOT_SUPPORTED | The currency is not enabled by GetPay. details.supported lists the currencies you can use. |
| 400 | EHDM_CURRENCY_NOT_SUPPORTED | Fiscal receipts are AMD only. Use AMD or send ehdm.skip: true. |
| 401 | UNAUTHORIZED | Missing or invalid secret key. |
| 403 | FORBIDDEN | The key can’t do this, e.g. a live key before your account is live. |
| 404 | NOT_FOUND | No such object for this key and environment. |
| 409 | CONFLICT | Not possible in the current state, e.g. reversing after 72 hours, refunding an unpaid payment or expiring a paid link. message says what to do instead. |
| 422 | VALIDATION_ERROR | A field is missing or invalid. details.errors lists each field. |
| 502 | PAYMENT_GATEWAY_ERROR | The bank rejected or didn’t answer a refund. Safe to retry later. |
13Go-live checklist
- Your domain is connected under Domain, and your server uses the API base URL shown there.
- Your merchant account has completed onboarding and is switched to live in the dashboard.
- Your bank (Ameriabank vPOS) credentials are connected and enabled under Payment gateways → Live.
- Your server uses the
sk_live_…key from an environment variable, never from code. - A live webhook endpoint is added with an
https://URL, and itswhsec_…secret is on your server. - Your webhook handler verifies signatures, rejects old timestamps and ignores duplicate event IDs.
- You fulfil orders only after confirming
complete/paidand matching amount and currency. - Your thank-you page or order email shows the customer the
receipt_url. - If you use EHDM: PayX is connected and enabled under Settings → EHDM, and your links send
line_items[].ehdm. - You made one small real payment and refunded it.