Push Push Payments Gateway API
Version 1 — server-to-server, JSON over HTTPS. Operated by Push Push Payments Ltd.
X-Api-Key— your key idX-Timestamp— Unix seconds, valid for 5 minutesX-Signature— hex HMAC-SHA256 of{timestamp}.{METHOD}.{path}.{rawBody}using your API secretIdempotency-Key— optional, safe retries on POST
const base = `${ts}.POST./api/public/v1/payments.${body}`;
const signature = crypto.createHmac("sha256", secret).update(base).digest("hex");{
"order_id": "A-1001",
"amount": 4990, // minor units
"currency": "EUR", // EUR | USD
"description": "Order A-1001",
"return_url": "https://shop.example/thanks",
"callback_url": "https://shop.example/hooks/pushpush",
"customer": { "email": "buyer@example.com", "country": "DE" },
"card": {
"number": "4012888888881881",
"exp_month": 12,
"exp_year": 2030,
"cvv": "123",
"holder": "JOHN DOE"
},
"test_scenario": "success" // sandbox only: success | decline | pending | error
}card is required for live keys. The card number is never stored — only the brand and last four digits are kept for reconciliation.
{
"id": "1f0c...",
"object": "payment",
"status": "succeeded", // succeeded | failed | pending
"amount": 4990,
"currency": "EUR",
"payment_url": null, // set when the bank requires 3-D Secure
"refunded_amount": 0,
"failure": null,
"metadata": { "card": { "brand": "visa", "last4": "1881" } }
}When status is pending and payment_url is present, redirect the shopper there to complete bank verification. The final result arrives as a webhook.
4012888888881881— Visa, approved4111111111111112— Visa, declined4539148803436467— Visa, 3-D Secure2223000048450011— Mastercard, approved5300000000000006— Mastercard, declined5454545454545454— Mastercard, 3-D Secure
1. Create the payment. If the bank requires verification, the response has status: "pending" and a payment_url on our domain.
2. Redirect the shopper to that URL — the bank page loads inside it.
3. After the bank step we return the shopper to your return_url with payment_id and status query parameters.
4. The authoritative result always arrives as a signed webhook.
For GET requests the signature base string uses an empty body.
{ "payment_id": "1f0c...", "amount": 2000, "reason": "partial return" }Verify X-Signature as HMAC-SHA256 of {X-Timestamp}.{rawBody}. Respond 2xx; failures are retried with backoff up to 6 times.
{
"id": "evt_...",
"type": "payment.succeeded",
"created_at": "2026-01-01T10:00:00.000Z",
"data": { "id": "1f0c...", "status": "succeeded", "amount": 4990 }
}success— settles immediately assucceededdecline— settles immediately asfailedpendingor omitted — returns a hosted checkout link you can complete manuallyerror— simulates a routing failure (HTTP 502)