info@target.co.uk
Developers

Built for developers

One REST API for payments, refunds, payouts and subscriptions. Predictable JSON, idempotent requests, signed webhooks and a sandbox that behaves exactly like production — so your first payment takes an afternoon, not a sprint. Sandbox keys are issued the moment you sign up; a live account is approved in 2–5 business days, and the code you wrote against the sandbox does not change.

REST Webhooks OAuth2 Idempotency Sandbox
Quick start

Your first payment in four steps

Everything below runs against the sandbox with no contract, no card of your own and nothing to install beyond an HTTP client. Amounts are integers in minor units (4990 is €49.90), every response is JSON and every request that creates something accepts an Idempotency-Key, so a retried call never charges twice.

01

Get sandbox keys

Sign up in the dashboard and open Developers → API keys. You get a public key for the browser widget and a secret key for your server. Sandbox keys start with sk_test_; live keys with sk_live_.

02

Create a payment

Call POST /v1/payments with an amount, a currency and a return URL. The response carries the payment id, its status and a checkout_url for the hosted page — or a client secret if you render the widget yourself.

03

Redirect / confirm 3DS

Send the customer to checkout_url. Target tokenises the card and runs 3-D Secure 2.2 — frictionless where the issuer allows it, a challenge screen where it insists — then returns the customer to your return_url.

04

Listen to webhooks

Register an HTTPS endpoint, verify the signature and mark the order paid on payment.succeeded. Never trust the redirect alone: browsers close, networks drop — the webhook is the source of truth.

Create a payment

bash# Basic auth: secret key as username, empty password
curl https://api.target.co.uk/v1/payments \
  -u "sk_test_9f2b4c7e1a6d3e8f:" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-10482" \
  -d '{
    "amount": 4990,
    "currency": "EUR",
    "description": "Order #10482",
    "customer": { "email": "anna@example.com" },
    "return_url": "https://shop.example.com/return",
    "metadata": { "order_id": "10482" }
  }'
json · 201 Created{
  "id": "pay_7f3c2a9e",
  "object": "payment",
  "status": "pending",
  "amount": 4990,
  "currency": "EUR",
  "description": "Order #10482",
  "checkout_url": "https://pay.target.co.uk/c/7f3c2a9e",
  "three_ds": { "required": true, "version": "2.2.0" },
  "livemode": false,
  "metadata": { "order_id": "10482" },
  "created_at": "2026-09-14T09:12:41Z"
}
javascriptimport Target from '@target-fs/node';

const target = new Target(process.env.TARGET_SECRET_KEY);

const payment = await target.payments.create({
  amount: 4990,                // minor units → €49.90
  currency: 'EUR',
  description: 'Order #10482',
  customer: { email: 'anna@example.com' },
  return_url: 'https://shop.example.com/return',
  metadata: { order_id: '10482' },
}, { idempotencyKey: 'order-10482' });

res.redirect(payment.checkout_url);
json · 201 Created{
  "id": "pay_7f3c2a9e",
  "object": "payment",
  "status": "pending",
  "amount": 4990,
  "currency": "EUR",
  "description": "Order #10482",
  "checkout_url": "https://pay.target.co.uk/c/7f3c2a9e",
  "three_ds": { "required": true, "version": "2.2.0" },
  "livemode": false,
  "metadata": { "order_id": "10482" },
  "created_at": "2026-09-14T09:12:41Z"
}
pythonimport os
import target

client = target.Client(api_key=os.environ["TARGET_SECRET_KEY"])

payment = client.payments.create(
    amount=4990,  # minor units → €49.90
    currency="EUR",
    description="Order #10482",
    customer={"email": "anna@example.com"},
    return_url="https://shop.example.com/return",
    metadata={"order_id": "10482"},
    idempotency_key="order-10482",
)

return redirect(payment.checkout_url)
json · 201 Created{
  "id": "pay_7f3c2a9e",
  "object": "payment",
  "status": "pending",
  "amount": 4990,
  "currency": "EUR",
  "description": "Order #10482",
  "checkout_url": "https://pay.target.co.uk/c/7f3c2a9e",
  "three_ds": { "required": true, "version": "2.2.0" },
  "livemode": false,
  "metadata": { "order_id": "10482" },
  "created_at": "2026-09-14T09:12:41Z"
}
php<?php
require 'vendor/autoload.php';

$target = new \Target\Client(getenv('TARGET_SECRET_KEY'));

$payment = $target->payments->create([
  'amount'      => 4990,           // minor units → €49.90
  'currency'    => 'EUR',
  'description' => 'Order #10482',
  'customer'    => ['email' => 'anna@example.com'],
  'return_url'  => 'https://shop.example.com/return',
  'metadata'    => ['order_id' => '10482'],
], ['idempotency_key' => 'order-10482']);

header('Location: ' . $payment->checkout_url);
json · 201 Created{
  "id": "pay_7f3c2a9e",
  "object": "payment",
  "status": "pending",
  "amount": 4990,
  "currency": "EUR",
  "description": "Order #10482",
  "checkout_url": "https://pay.target.co.uk/c/7f3c2a9e",
  "three_ds": { "required": true, "version": "2.2.0" },
  "livemode": false,
  "metadata": { "order_id": "10482" },
  "created_at": "2026-09-14T09:12:41Z"
}

The same code goes live when you swap sk_test_ for sk_live_. A payment stays pending until the customer finishes 3-D Secure, then becomes succeeded or failed — poll GET /v1/payments/{id} if you must, but a webhook will reach you first. Prefer not to redirect? The embedded widget keeps the customer on your page and uses the same payment object.

SDKs & plugins

Your language, your platform

Official libraries wrap the API with typed models, automatic retries and idempotency keys generated for you. If your shop runs on a platform, install the plugin instead and write no code at all: payment methods, refunds and order statuses sync straight into your admin panel. Everything is open source and versioned with semver.

JavaScript Node.js Python PHP Java Kotlin/Swift Shopify WooCommerce Magento OpenCart PrestaShop Tilda

Server libraries

Node.js, Python, PHP and Java on npm, PyPI, Packagist and Maven Central. Each release is tested against the live API before it ships; breaking changes only in a major version, with a migration note.

Client & mobile

The JavaScript widget for the browser plus native Swift and Kotlin SDKs: card form, Apple Pay and Google Pay, 3-D Secure challenge handled in a sheet. Card data goes from the device straight to Target — your servers stay out of PCI scope.

Plugins

Shopify, WooCommerce, Magento, OpenCart, PrestaShop and Tilda. Install, paste your keys, pick the payment methods — live in about twenty minutes. Building a platform of your own? See the partner programme.

Webhooks

Every event,in real time

Target pushes a signed JSON event to your endpoint the moment anything changes: an authorisation, a refund, a dispute, a payout leaving our bank. Events are delivered at least once and in order per object; if your endpoint does not answer 2xx we retry with exponential back-off for 72 hours, and you can replay any event from the dashboard.

EventWhen it firesWhat you usually do
payment.succeededThe issuer approved the authorisation and, unless capture is manual, the amount was captured.Mark the order paid and start fulfilment. Store the payment id against the order for refunds later.
payment.failedThe issuer declined, 3-D Secure failed or the customer abandoned checkout after 30 minutes.Release the reserved stock and show a retry with the decline_code from the payload.
refund.createdA full or partial refund was accepted for processing — from the API, the dashboard or a plugin.Update the order total, credit loyalty points and e-mail the customer; funds reach the card in 3–5 days.
chargeback.openedThe cardholder disputed a payment with their bank. The amount plus the €15 fee is held from your balance.Upload evidence in the dashboard within 7 days. Our alerts often reach you before the dispute does.
payout.sentA settlement batch left Target's bank account towards your card, IBAN or e-wallet.Reconcile the batch against the payout report attached to the event.
subscription.renewedA recurring charge succeeded for the next billing period on a saved card or network token.Extend access to the next period and send the invoice. A failed renewal fires payment.failed instead.

The envelope

Every event has the same shape: an id, a type, a timestamp and the full object in data. Use the id to de-duplicate — the same event may arrive twice, never a different one under the same id.

json · POST https://shop.example.com/hooks/target{
  "id": "evt_01j7q3x8k2",
  "type": "payment.succeeded",
  "created_at": "2026-09-14T09:12:58Z",
  "livemode": true,
  "data": {
    "id": "pay_7f3c2a9e",
    "status": "succeeded",
    "amount": 4990,
    "currency": "EUR",
    "captured": true,
    "three_ds": { "result": "authenticated", "flow": "frictionless" },
    "metadata": { "order_id": "10482" }
  }
}

Verify the signature

Each call carries a Target-Signature header: a timestamp and an HMAC-SHA256 of timestamp.body keyed with your endpoint secret. Check it on the raw body, before parsing, and reject anything older than five minutes.

  • Answer 2xx within 10 seconds; do the work afterwards
  • One secret per endpoint, rotated from the dashboard without downtime
  • Sandbox events carry livemode: false — same shape, same signature
javascriptimport crypto from 'node:crypto';

export function verify(rawBody, header, secret) {
  // Target-Signature: t=1726305178,v1=5f1c…e2
  const { t, v1 } = Object.fromEntries(
    header.split(',').map(part => part.split('='))
  );
  const expected = crypto
    .createHmac('sha256', secret)
    .update(`${t}.${rawBody}`)
    .digest('hex');
  const fresh = Math.abs(Date.now() / 1000 - Number(t)) < 300;
  return fresh && crypto.timingSafeEqual(
    Buffer.from(expected), Buffer.from(v1)
  );
}
Sandbox

Sandbox & test cards

The sandbox is a full copy of production — the same endpoints, the same dashboard, the same webhooks — only the money is not real. Use the cards below to script every outcome your integration must handle, from a clean approval to a 3-D Secure challenge and a decline. Any future expiry date and any three-digit CVC will do.

Card numberBrandResultUse it to test
4111 1111 1111 1111VisaSucceededThe happy path: frictionless 3-D Secure, instant authorisation, a payment.succeeded webhook within a second.
4000 0000 0000 0002VisaDeclinedYour decline handling and retry screen. The payment fails with decline_code: do_not_honour and fires payment.failed.
4000 0000 0000 3220Visa3DS challengeThe challenge flow: the customer sees a one-time-code screen (use 1234), then returns to your return_url.
5555 5555 5555 4444MastercardSucceededBrand detection in the card form, Mastercard routing and the scheme-specific fields in the payment object.
TestSandbox · sk_test_
Base URL
The same host as production, https://api.target.co.uk. The environment is chosen by the key: sk_test_ talks to the sandbox, sk_live_ to live.
Special amounts
Any amount works on the cards above. End it in 05 for insufficient_funds, 10 for fraud_suspected, 20 for expired_card — the decline codes you will see in production.
Refunds & payouts
Refunds succeed instantly. Payouts move to sent after 60 seconds so you can test the payout.sent webhook without waiting for a bank.
Webhooks
Register up to 10 endpoints per environment. Every event can be re-sent from the dashboard, and a “Send test event” button lets you check your signature code before the first real payment.
Data
Sandbox objects are kept for 90 days and are never mixed with live data. Teams can share one sandbox with per-user roles, exactly as in the live dashboard.
API reference

The endpoints you will call every day

The API is resource-oriented: create an object with POST, read it with GET, and every list supports cursor pagination and filtering by status, date and metadata. Amounts are integers in minor units, timestamps are ISO 8601 in UTC, and every error carries a machine-readable code next to a sentence a human can act on.

POST/v1/payments
Create a payment. Returns a checkout_url for the hosted page or a client_secret for the widget. Supports automatic or manual capture, 3-D Secure, saved cards, network tokens and up to 20 metadata keys.
GET/v1/payments/{id}
Retrieve a payment with its current status, the 3-D Secure result, the acquirer response code, fees charged and the list of refunds. Add ?expand=customer to inline related objects.
POST/v1/refunds
Refund a captured payment in full or in part. Several partial refunds may be issued up to the captured amount; each one fires refund.created and appears on the customer’s statement in 3–5 days.
POST/v1/payouts
Send funds from your balance to a card, IBAN or e-wallet in any of 25 settlement currencies. Batch up to 1,000 recipients in one request; each item is validated and reported separately.
POST/v1/subscriptions
Start a recurring plan on a saved card: interval, trial period, proration and a retry schedule for failed renewals. Renewals fire subscription.renewed; see recurring payments.
GET/v1/balances
Available and pending balance per settlement currency, the date and amount of the next scheduled payout and the sum currently held against open chargebacks.

Authentication

Your secret key over HTTP Basic auth, TLS 1.2 or higher. Platforms that act for many merchants use OAuth2 client credentials and receive a short-lived token scoped to one merchant account.

Versioning

Date-based. Your account is pinned to the version current when you created it; send Target-Version: 2026-06-01 to try a newer one per request before you upgrade the account.

Errors

Conventional HTTP codes — 4xx for something in the request, 5xx for something on our side — with a JSON body carrying error.code, error.message and, where relevant, the offending error.param. Card declines are 402 with the issuer’s decline code.

Status & limits

Boring, on purpose

Payments infrastructure should be the least interesting part of your stack. The API runs in three European regions behind independent acquirer connections, deploys without downtime and is measured from outside every ten seconds. The numbers below are the trailing twelve months, not a launch-day snapshot.

99.99%
API uptime, trailing 12 months
180 ms
p95 latency to create a payment, EU region
600
Requests per minute per API key

Rate limits

Every API key may make 600 requests per minute, counted separately for sandbox and live. Above that we answer 429 Too Many Requests with a Retry-After header instead of dropping the call; short bursts of up to 100 requests per second are tolerated for five seconds so a flash sale does not trip the limit. Three headers on every response tell you where you stand, and the SDKs back off automatically. Marketplaces and ticketing merchants that need more get a higher ceiling on Enterprise — ask your manager.

httpHTTP/1.1 429 Too Many Requests
Retry-After: 12
X-RateLimit-Limit: 600
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1726305180

Idempotency

Send an Idempotency-Key header — any string up to 255 characters, your order id is a good one — with every POST. If the connection drops and you retry with the same key and the same body within 24 hours, you receive the original response; the customer is never charged twice. The same key with a different body is rejected with 422 idempotency_key_reused, which is how you catch a bug before it costs money. The SDKs generate a key per call unless you pass your own.

httpPOST /v1/payments
Idempotency-Key: order-10482

→ 201 Created   first call
→ 201 Created   retry, same body
→ 422 Unprocessable   new body

Live status and a 90-day incident history are in the dashboard under Developers → Status, and every incident is announced to your webhook endpoint as it opens and closes. Questions about the API go to the same 24/7 team that runs it; on Business and Enterprise your personal manager can put an engineer on the call.

Help

Still have questions? We will help!

By clicking “Send” you agree to the Privacy policy and the processing of your personal data.

Thank you! Your request has been sent — a manager will contact you within one business day.