Tracking & Webhooks
Monitor payment status in real-time via the Lookup API and receive webhook notifications when payment status changes.
Payment Lookup API
Query payment status by payment ID or provider payment ID.
Endpoint
GET /api/lookup/{id}Authentication
Include your API key in the x-api-key header:
curl -X GET "https://surstrom.io/api/lookup/a1b2c3d4-e5f6-7890-abcd-ef1234567890" \
-H "x-api-key: your-api-key"Lookup Methods
| Lookup Type | Format | Access |
|---|---|---|
| Payment UUID | a1b2c3d4-e5f6-... | Relay (own payments) |
| Provider Payment ID | pi_3Abc123... | Relay (own payments) |
Response
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"provider": "stripe",
"providerPaymentId": "pi_3Abc123Def456Ghi789",
"type": "PAYMENT",
"createdAt": "2025-01-15T10:30:00.000Z",
"updatedAt": "2025-01-15T10:35:00.000Z",
"status": "COMPLETED",
"rawLog": {
"id": "pi_3Abc123...",
"status": "succeeded",
"amount": 10000
},
"providerId": "provider-uuid",
"relayId": "relay-user-uuid",
"clientId": "client-user-uuid",
"relayPage": "https://your-site.com/checkout?id=a1b2c3d4...",
"description": "Order payment",
"suffix": "MYSHOP",
"suffixClient": "ORDER123",
"amount": 100.00,
"currency": "usd",
"fee": 7,
"netAmount": 5.25,
"send": 93.00,
"relayWallet": "RelayWalletPublicKey...",
"clientWallet": "ClientWalletPublicKey...",
"txHash": "5UxG7jK2mN...",
"deliveryTime": "2025-01-15T10:35:00.000Z",
"card": "4242",
"cardholder": "John Doe",
"country": "US",
"bin": "424242",
"brand": "visa",
"cardFingerprint": "fp_abc123...",
"fraudScore": 12,
"ip": "192.168.1.1",
"theme": "light",
"sent": "TRUE"
}Response Fields
| Field | Type | Description |
|---|---|---|
id | string | Payment UUID |
type | string | Always "PAYMENT" |
status | string | Payment status (see lifecycle below) |
provider | string | Provider type (stripe) |
providerPaymentId | string | Provider's payment identifier (pi_xxx) |
providerId | string | Provider UUID |
relayId | string | Relay user UUID |
clientId | string | Client user UUID |
amount | number | Gross payment amount in major units |
currency | string | Currency code (usd, eur, etc.) |
fee | number | Fee percentage |
send | number | Amount sent to Client |
netAmount | number | Your net earnings |
relayPage | string | Checkout page URL with payment ID |
description | string | Payment description (if configured) |
suffix | string | Statement descriptor suffix |
suffixClient | string | Client's statement descriptor suffix |
relayWallet | string | Your Solana wallet address |
clientWallet | string | Client's Solana wallet address |
txHash | string | Solana transaction signature (when settled) |
txError | string | Settlement error message (if failed) |
deliveryTime | string | Settlement timestamp (ISO 8601) |
card | string | Last 4 digits of card |
cardholder | string | Name on card |
country | string | Card issuing country (ISO code) |
bin | string | Card BIN (first 6 digits) |
brand | string | Card brand (visa, mastercard, amex) |
cardFingerprint | string | Unique card fingerprint for deduplication |
fraudScore | number | Fraud risk score (0-100) |
ip | string | Customer IP address |
decline | string | Decline reason from provider |
sent | string | Settlement status: TRUE, FALSE, or FAILED |
errorDesc | string | Error message if payment failed |
rawLog | object | Full provider API response |
exchangeRoute | object | Cross-currency conversion details |
createdAt | string | Payment creation timestamp (ISO 8601) |
updatedAt | string | Last update timestamp (ISO 8601) |
theme | string | Payment form theme (light or dark) |
Error Responses
{
"error": "Payment not found"
}{
"error": "API key required"
}Webhooks
Receive HTTP POST notifications when payment status changes.
Configuration
Configure your webhook URL in the provider settings page. The URL must:
- Be publicly accessible (HTTPS recommended)
- Return HTTP 200 to confirm receipt
- Respond within 5 seconds
Private IP ranges (localhost, 10.x, 192.168.x, 172.16-31.x) are blocked for security.
Webhook Delivery
| Property | Value |
|---|---|
| Method | POST |
| Content-Type | application/json |
| Header | X-Payment-Event: payment.event |
| Retries | None (fire-and-forget) |
Webhooks are best-effort delivery. Always use the Lookup API as the source of truth for payment status.
Webhook Events
Webhooks are fired when:
- Payment created — Initial
PENDINGstatus - Payment confirmed — Customer completed payment form, status changes to
PROCESSING - Settlement complete — USDC delivered, status changes to
COMPLETED - Payment failed — Card declined or error, status changes to
FAILED - Payment canceled — Timeout or manual cancel, status changes to
CANCELED - Payment refunded — Refund processed, status changes to
REFUNDED - Payment disputed — Dispute opened, status changes to
DISPUTED
Webhook Payload
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"provider": "stripe",
"providerPaymentId": "pi_3Abc123Def456Ghi789",
"type": "PAYMENT",
"createdAt": "2025-01-15T10:30:00.000Z",
"updatedAt": "2025-01-15T10:35:00.000Z",
"status": "COMPLETED",
"rawLog": {
"id": "pi_3Abc123...",
"status": "succeeded",
"amount": 10000,
"currency": "usd"
},
"providerId": "provider-uuid",
"relayId": "relay-user-uuid",
"clientId": "client-user-uuid",
"relayPage": "https://your-site.com/checkout?id=a1b2c3d4...",
"description": "Order payment",
"suffix": "MYSHOP",
"suffixClient": "ORDER123",
"amount": 100.00,
"currency": "usd",
"fee": 7,
"netAmount": 5.25,
"send": 93.00,
"relayWallet": "RelayWalletPublicKey...",
"clientWallet": "ClientWalletPublicKey...",
"txHash": "5UxG7jK2mN...",
"deliveryTime": "2025-01-15T10:35:00.000Z",
"card": "4242",
"cardholder": "John Doe",
"country": "US",
"bin": "424242",
"brand": "visa",
"cardFingerprint": "fp_abc123...",
"fraudScore": 12,
"ip": "192.168.1.1",
"theme": "light",
"sent": "TRUE"
}Payload by Status
PENDING — Payment created, waiting for customer:
status:"PENDING"sent:"FALSE"- No card details yet
PROCESSING — Payment confirmed, awaiting settlement:
status:"PROCESSING"sent:"FALSE"- Card details populated
COMPLETED — Settlement successful:
status:"COMPLETED"sent:"TRUE"txHash: Solana transaction signaturedeliveryTime: Settlement timestamp
FAILED — Payment failed:
status:"FAILED"sent:"FALSE"errorDesc: Failure reason (e.g., "Your card was declined")
CANCELED — Payment timed out or canceled:
status:"CANCELED"sent:"FALSE"
REFUNDED — Payment was refunded:
status:"REFUNDED"sent:"FALSE"- Original settlement may have been reversed
DISPUTED — Payment is under dispute:
status:"DISPUTED"sent: varies (may be"TRUE"if settled before dispute)
CRASH — System error during processing:
status:"CRASH"sent:"FALSE"txError: Error details if settlement failed
Cross-Currency Payments
For payments converted from another currency:
{
"exchangeRoute": {
"originalValue": 100.00,
"clientCurrency": "usd",
"nativeCurrency": "eur",
"rate": 0.92,
"result": 92.00
}
}Testing Webhooks
Test your webhook endpoint from the provider settings page. The test payload:
{
"event": "test",
"timestamp": "2025-01-15T10:30:00.000Z",
"provider_id": "your-provider-uuid",
"message": "This is a test webhook from surstrom"
}Handling Webhooks
Example webhook handler:
app.post('/webhook/surstrom', (req, res) => {
const event = req.headers['x-payment-event']
const payload = req.body
// Test webhook
if (payload.event === 'test') {
console.log('Test webhook received')
return res.status(200).send('OK')
}
// Payment webhook
const { id, status, amount, currency, txHash } = payload
switch (status) {
case 'PROCESSING':
// Payment confirmed, awaiting settlement
console.log(`Payment ${id} confirmed for ${amount} ${currency}`)
break
case 'COMPLETED':
// Settlement complete
console.log(`Payment ${id} settled. TX: ${txHash}`)
// Fulfill order
break
case 'FAILED':
console.log(`Payment ${id} failed: ${payload.errorDesc}`)
break
}
res.status(200).send('OK')
})Always return HTTP 200 quickly. Process webhooks asynchronously to avoid timeouts.
Payment Lifecycle
| Status | Description | sent |
|---|---|---|
PENDING | Created, waiting for customer | FALSE |
PROCESSING | Customer paid, awaiting settlement | FALSE |
COMPLETED | Settlement delivered | TRUE |
FAILED | Payment failed | FALSE |
CANCELED | Timed out or canceled | FALSE |
CRASH | System error (rare) | FALSE |
REFUNDED | Refunded through provider | FALSE |
DISPUTED | Under dispute | varies |
Best Practices
Idempotency
You may receive the same webhook multiple times. Use the id field to deduplicate:
const processedPayments = new Set()
app.post('/webhook/surstrom', (req, res) => {
const { id, status } = req.body
const key = `${id}:${status}`
if (processedPayments.has(key)) {
return res.status(200).send('Already processed')
}
processedPayments.add(key)
// Process payment...
res.status(200).send('OK')
})Verify with Lookup API
Since webhooks are fire-and-forget, always verify payment status via the Lookup API before taking critical actions:
app.post('/webhook/surstrom', async (req, res) => {
const { id, status } = req.body
// Acknowledge webhook immediately
res.status(200).send('OK')
// Verify status before fulfilling order
if (status === 'COMPLETED') {
const payment = await fetch(`https://surstrom.io/api/lookup/${id}`, {
headers: { 'x-api-key': API_KEY }
}).then(r => r.json())
if (payment.status === 'COMPLETED' && payment.sent === 'TRUE') {
await fulfillOrder(payment)
}
}
})Async Processing
Process webhooks asynchronously to avoid timeouts:
app.post('/webhook/surstrom', (req, res) => {
// Respond immediately
res.status(200).send('OK')
// Queue for async processing
paymentQueue.add(req.body)
})Rate Limits
| Endpoint | Limit |
|---|---|
| Lookup API | 100 requests/minute |
| Webhook test | 5 requests/minute |