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

Lookup endpointtext
GET /api/lookup/{id}

Authentication

Include your API key in the x-api-key header:

Requestbash
curl -X GET "https://surstrom.io/api/lookup/a1b2c3d4-e5f6-7890-abcd-ef1234567890" \
-H "x-api-key: your-api-key"

Lookup Methods

Lookup TypeFormatAccess
Payment UUIDa1b2c3d4-e5f6-...Relay (own payments)
Provider Payment IDpi_3Abc123...Relay (own payments)

Response

Lookup Responsejson
{
"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

FieldTypeDescription
idstringPayment UUID
typestringAlways "PAYMENT"
statusstringPayment status (see lifecycle below)
providerstringProvider type (stripe)
providerPaymentIdstringProvider's payment identifier (pi_xxx)
providerIdstringProvider UUID
relayIdstringRelay user UUID
clientIdstringClient user UUID
amountnumberGross payment amount in major units
currencystringCurrency code (usd, eur, etc.)
feenumberFee percentage
sendnumberAmount sent to Client
netAmountnumberYour net earnings
relayPagestringCheckout page URL with payment ID
descriptionstringPayment description (if configured)
suffixstringStatement descriptor suffix
suffixClientstringClient's statement descriptor suffix
relayWalletstringYour Solana wallet address
clientWalletstringClient's Solana wallet address
txHashstringSolana transaction signature (when settled)
txErrorstringSettlement error message (if failed)
deliveryTimestringSettlement timestamp (ISO 8601)
cardstringLast 4 digits of card
cardholderstringName on card
countrystringCard issuing country (ISO code)
binstringCard BIN (first 6 digits)
brandstringCard brand (visa, mastercard, amex)
cardFingerprintstringUnique card fingerprint for deduplication
fraudScorenumberFraud risk score (0-100)
ipstringCustomer IP address
declinestringDecline reason from provider
sentstringSettlement status: TRUE, FALSE, or FAILED
errorDescstringError message if payment failed
rawLogobjectFull provider API response
exchangeRouteobjectCross-currency conversion details
createdAtstringPayment creation timestamp (ISO 8601)
updatedAtstringLast update timestamp (ISO 8601)
themestringPayment form theme (light or dark)

Error Responses

Not Foundjson
{
"error": "Payment not found"
}
Unauthorizedjson
{
"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

PropertyValue
MethodPOST
Content-Typeapplication/json
HeaderX-Payment-Event: payment.event
RetriesNone (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:

  1. Payment created — Initial PENDING status
  2. Payment confirmed — Customer completed payment form, status changes to PROCESSING
  3. Settlement complete — USDC delivered, status changes to COMPLETED
  4. Payment failed — Card declined or error, status changes to FAILED
  5. Payment canceled — Timeout or manual cancel, status changes to CANCELED
  6. Payment refunded — Refund processed, status changes to REFUNDED
  7. Payment disputed — Dispute opened, status changes to DISPUTED

Webhook Payload

Relay Webhook Payloadjson
{
"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 signature
  • deliveryTime: 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:

Exchange Routejson
{
"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:

Test Webhookjson
{
"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:

Webhook Handlerjavascript
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

StatusDescriptionsent
PENDINGCreated, waiting for customerFALSE
PROCESSINGCustomer paid, awaiting settlementFALSE
COMPLETEDSettlement deliveredTRUE
FAILEDPayment failedFALSE
CANCELEDTimed out or canceledFALSE
CRASHSystem error (rare)FALSE
REFUNDEDRefunded through providerFALSE
DISPUTEDUnder disputevaries

Best Practices

Idempotency

You may receive the same webhook multiple times. Use the id field to deduplicate:

Idempotent Handlerjavascript
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:

Verify Before Actionjavascript
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:

Queue-Based Processingjavascript
app.post('/webhook/surstrom', (req, res) => {
// Respond immediately
res.status(200).send('OK')

// Queue for async processing
paymentQueue.add(req.body)
})

Rate Limits

EndpointLimit
Lookup API100 requests/minute
Webhook test5 requests/minute