Authentication
Include your API key in the Authorization header as a Bearer token.
Find your API keys in Admin → Settings → Checkout API Keys. Use test keys for development and live keys for production.
Authorization: Bearer sk_live_your_api_key
/sessions
Create Checkout Session
Create a new checkout session and redirect your customer to the returned URL. After payment, they'll be redirected to your success_url.
Parameters
amount_cents
required
integer
Amount in cents. 2500 = €25.00
currency
required
string
Three-letter ISO code: eur, usd, gbp
success_url
required
string
Redirect URL after payment. We append ?session_id={id}
cancel_url
required
string
Redirect URL if customer cancels
customer_email
string
Email for invoice delivery
metadata
object
Key-value pairs returned in webhooks
billing_details
object
Name, address, tax_id for invoices
curl -X POST https://jopay.me/api/checkout/v1/sessions \
-H "Authorization: Bearer sk_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"amount_cents": 2500,
"currency": "eur",
"customer_email": "customer@example.com",
"success_url": "https://yoursite.com/success",
"cancel_url": "https://yoursite.com/cancel",
"metadata": {"order_id": "12345"}
}'
require 'net/http'
require 'json'
uri = URI('https://jopay.me/api/checkout/v1/sessions')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post.new(uri)
request['Authorization'] = 'Bearer sk_live_your_api_key'
request['Content-Type'] = 'application/json'
request.body = {
amount_cents: 2500,
currency: 'eur',
customer_email: 'customer@example.com',
success_url: 'https://yoursite.com/success',
cancel_url: 'https://yoursite.com/cancel'
}.to_json
response = http.request(request)
session = JSON.parse(response.body)
redirect_to session['url']
import requests
response = requests.post(
'https://jopay.me/api/checkout/v1/sessions',
headers={
'Authorization': 'Bearer sk_live_your_api_key',
'Content-Type': 'application/json'
},
json={
'amount_cents': 2500,
'currency': 'eur',
'customer_email': 'customer@example.com',
'success_url': 'https://yoursite.com/success',
'cancel_url': 'https://yoursite.com/cancel'
}
)
session = response.json()
return redirect(session['url'])
const response = await fetch('https://jopay.me/api/checkout/v1/sessions', {
method: 'POST',
headers: {
'Authorization': 'Bearer sk_live_your_api_key',
'Content-Type': 'application/json'
},
body: JSON.stringify({
amount_cents: 2500,
currency: 'eur',
customer_email: 'customer@example.com',
success_url: 'https://yoursite.com/success',
cancel_url: 'https://yoursite.com/cancel'
})
});
const session = await response.json();
res.redirect(session.url);
Response
{
"id": "cs_a1b2c3d4e5f6...",
"url": "https://jopay.me/checkout/cs_a1b2c3d4e5f6",
"status": "open",
"amount_cents": 2500,
"currency": "eur",
"customer_email": "customer@example.com",
"expires_at": "2025-01-10T13:00:00Z"
}
/sessions/:id
Retrieve Session
Retrieve an existing session to check payment status. Use this to verify payment completion on your success page.
Status Values
open
Session is active and awaiting payment
completed
Payment was successful
expired
Session expired without payment (1 hour timeout)
curl https://jopay.me/api/checkout/v1/sessions/cs_a1b2c3d4e5f6 \ -H "Authorization: Bearer sk_live_your_api_key"
require 'net/http'
require 'json'
session_id = 'cs_a1b2c3d4e5f6'
uri = URI("https://jopay.me/api/checkout/v1/sessions/#{session_id}")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Get.new(uri)
request['Authorization'] = 'Bearer sk_live_your_api_key'
response = http.request(request)
session = JSON.parse(response.body)
if session['status'] == 'completed'
# Fulfill the order
end
import requests
session_id = 'cs_a1b2c3d4e5f6'
response = requests.get(
f'https://jopay.me/api/checkout/v1/sessions/{session_id}',
headers={
'Authorization': 'Bearer sk_live_your_api_key'
}
)
session = response.json()
if session['status'] == 'completed':
# Fulfill the order
pass
const sessionId = 'cs_a1b2c3d4e5f6';
const response = await fetch(
`https://jopay.me/api/checkout/v1/sessions/${sessionId}`,
{
headers: {
'Authorization': 'Bearer sk_live_your_api_key'
}
}
);
const session = await response.json();
if (session.status === 'completed') {
// Fulfill the order
}
Webhooks
Configure your webhook endpoint in Admin → Settings → Checkout Webhook. We send events when payment status changes.
Events
checkout.session.completed
Payment successful. Fulfill the order.
checkout.session.expired
Session expired without payment.
Payload
{
"event": "checkout.session.completed",
"data": {
"id": "cs_a1b2c3d4e5f6...",
"status": "completed",
"amount_cents": 2500,
"currency": "eur",
"customer_email": "customer@example.com",
"metadata": {"order_id": "12345"},
"completed_at": "2025-01-10T12:05:00Z"
},
"timestamp": "2025-01-10T12:05:01Z"
}
Verifying Signatures
Verify the X-JoPay-Signature header to ensure the webhook is authentic. Compare the HMAC-SHA256 signature using your webhook secret.
# Verify signature using openssl
SIGNATURE="sha256=abc123..."
PAYLOAD='{"event":"checkout.session.completed",...}'
SECRET="whsec_your_webhook_secret"
EXPECTED=$(echo -n "$PAYLOAD" | openssl dgst -sha256 -hmac "$SECRET")
echo "sha256=$EXPECTED"
# Compare with received signature
def verify_signature(payload, signature, secret)
expected = OpenSSL::HMAC.hexdigest('SHA256', secret, payload)
ActiveSupport::SecurityUtils.secure_compare(
"sha256=#{expected}",
signature
)
end
# In your controller
def webhook
payload = request.body.read
signature = request.headers['X-JoPay-Signature']
unless verify_signature(payload, signature, ENV['WEBHOOK_SECRET'])
return head :unauthorized
end
# Process the event
end
import hmac
import hashlib
def verify_signature(payload, signature, secret):
expected = hmac.new(
secret.encode('utf-8'),
payload.encode('utf-8'),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(
f'sha256={expected}',
signature
)
# In your Flask/Django view
@app.route('/webhook', methods=['POST'])
def webhook():
payload = request.get_data(as_text=True)
signature = request.headers.get('X-JoPay-Signature')
if not verify_signature(payload, signature, WEBHOOK_SECRET):
return 'Unauthorized', 401
# Process the event
return 'OK', 200
const crypto = require('crypto');
function verifySignature(payload, signature, secret) {
const expected = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(`sha256=${expected}`),
Buffer.from(signature)
);
}
// In your Express route
app.post('/webhook', (req, res) => {
const payload = req.body;
const signature = req.headers['x-jopay-signature'];
if (!verifySignature(JSON.stringify(payload), signature, WEBHOOK_SECRET)) {
return res.status(401).send('Unauthorized');
}
// Process the event
res.status(200).send('OK');
});
Billing Details
Include billing details for B2B customers who need invoices with their company name and VAT number.
Tax ID Types
eu_vat
EU VAT Number
gb_vat
UK VAT Number
us_ein
US EIN
au_abn
AU ABN
{
"billing_details": {
"name": "Acme Corporation",
"address": {
"line1": "123 Business Street",
"city": "Lisbon",
"postal_code": "1000-001",
"country": "PT"
},
"tax_id": {
"type": "eu_vat",
"value": "PT123456789"
}
}
}
Errors
The API returns standard HTTP status codes with JSON error details.
Status Codes
200
Request succeeded
400
Invalid parameters
401
Invalid or missing API key
404
Resource not found
500
Server error
Error Response
{
"error": {
"code": "invalid_parameter",
"message": "amount_cents must be greater than 0",
"param": "amount_cents"
}
}