Skip to content

Webhooks & Callbacks

SecPaid provides two distinct notification mechanisms to inform you when a payment is completed or cancelled. Understanding the difference is critical for a robust integration.

Overview

Mechanism Type Triggered By Recipient Use Case
callback_url Browser redirect Customer's browser Customer + your frontend Show success/cancel page to customer
payment_endpoint Server-to-server POST SecPaid backend Your backend server Reliable payment confirmation for order fulfillment
sequenceDiagram
    participant C as Customer Browser
    participant S as SecPaid
    participant F as Your Frontend
    participant B as Your Backend

    C->>S: Completes payment
    S->>B: POST to payment_endpoint (server-to-server)
    S->>C: HTTP 302 redirect
    C->>F: GET callback_url?pay_id=123&status=Success

Always use payment_endpoint for order fulfillment

The callback_url is a browser redirect — the customer can close their browser before it fires. Never rely solely on callback_url to confirm a payment. Always use payment_endpoint for critical business logic.

callback_url (Browser Redirect)

What It Is

A URL that the customer's browser is redirected to after they complete or cancel a payment. This is a client-side redirect — it happens in the customer's browser.

How to Set It

You can provide callback_url in two ways:

Option A: Per-link (in the API call)

{
  "amount": 49.99,
  "callback_url": "https://yoursite.com/payment/result"
}

Option B: From your account's Callback URLs list

You can configure multiple callback URLs in the platform dashboard:

  1. Navigate to SettingsCallback URLs
  2. Add your URLs (e.g., https://yoursite.com/payment/done, https://shop.example.com/secpaid/callback)
  3. Click Save Settings

Then reference them by index (0-based) in your API calls:

{
  "amount": 49.99,
  "callback_url": "0"
}

This uses the first URL from your configured list. Use "1" for the second, etc.

Redirect Behavior

After payment, the customer is redirected with query parameters appended:

On success:

https://yoursite.com/payment/result?pay_id=12345&status=Success

On cancellation (only if link is cancellable):

https://yoursite.com/payment/result?pay_id=12345&status=cancel

Query Parameters

Parameter Type Description
pay_id integer The linktopay_id of the payment link
status string Success or cancel

Important Notes

  • The redirect happens in the customer's browser — if they close the tab, you won't receive it
  • The customer can manipulate query parameters — do not trust this for payment verification
  • Use this to show a "Thank you" or "Payment cancelled" page to the customer
  • If no callback_url is set, the customer sees SecPaid's default success/cancel page

payment_endpoint (Server-to-Server Webhook)

What It Is

A URL on your backend that SecPaid calls via HTTP POST after a payment is completed. This is a server-to-server call — reliable and not dependent on the customer's browser.

How to Set It

You can configure payment_endpoint in three ways:

Option A: Account-level default

Configure a default webhook URL in your SecPaid account settings (attribute paymentEndpoint). This applies to all your payment links unless overridden per-link.

Option B: Per-link (direct URL)

Override the account default by passing a URL directly in the API call:

{
  "amount": 49.99,
  "payment_endpoint": "https://yoursite.com/webhooks/secpaid"
}

Option C: Per-link (index reference)

If you have multiple webhook URLs configured in your account, pass a 0-based index:

{
  "amount": 49.99,
  "payment_endpoint": "0"
}

Multiple Endpoints

You can send webhooks to multiple URLs simultaneously by passing a comma-separated value:

{
  "payment_endpoint": "https://primary.com/webhook,https://backup.com/webhook"
}

Or mix indices and URLs:

{
  "payment_endpoint": "0,https://analytics.com/hook"
}

All listed endpoints receive the same POST. This is useful for sending payment notifications to multiple systems (e.g., order management + analytics + accounting).

Same applies to callback_url

The callback_url field also supports comma-separated values and index references, but only the first URL is used for the browser redirect.

Payload Structure

Standard (unencrypted) payload — application/x-www-form-urlencoded:

ResponseCode=1&data[pay_id]=12345&data[note]=Invoice+%231234&data[amount]=49.99&data[user_id]=abc-def-123&data[status]=Success

Parsed as:

{
  "ResponseCode": 1,
  "data": {
    "pay_id": 12345,
    "note": "Invoice #1234",
    "amount": 49.99,
    "user_id": "abc-def-123",
    "status": "Success"
  }
}
Field Type Description
ResponseCode integer Always 1 for webhook calls
data.pay_id integer The linktopay_id
data.note string The recipient_note you set when creating the link
data.amount number The amount that was paid (total)
data.user_id string Your internal user ID
data.status string "Success" or "cancel"

Encrypted Payload

If you have encryption enabled on your link, the payload is JSON with Content-Type: application/json:

{
  "data": ["<base64-encoded AES-256-CBC encrypted string>"]
}

When decrypted, the string contains the same JSON structure as above.

For split links with encryption, a second POST is sent to the same endpoint(s) containing the encrypted split breakdown:

{
  "data": ["<base64-encoded encrypted array of payment rows>"]
}

Decrypted:

[
  {"user_id": "owner-uuid", "user_token": null, "amount": 40.00, "service_fee": 1.16, "service_fee_psp": 0.29, "net_amount": 38.55, "pay_id": 12345, "payment_method": null},
  {"user_id": "recipient-uuid", "user_token": "tok_a", "amount": 30.00, "service_fee": 0.87, "service_fee_psp": 0.22, "net_amount": 28.91, "pay_id": 12345, "payment_method": null}
]

See Encryption for how to decrypt.

When It Fires

The payment_endpoint is called when:

  • A card payment is confirmed (payment provider → SecPaid → your endpoint) — status: "Success"
  • A bank transfer is approved by an admin — status: "Success"
  • A customer cancels the payment (if the link has a paymentEndpoint configured) — status: "cancel"

It is NOT called on:

  • Link deletion
  • Refunds (no webhook currently)

Handling the Webhook

Your endpoint should:

  1. Accept a POST request with Content-Type: application/json
  2. Verify the pay_id matches an expected payment
  3. Process the payment (fulfill order, send confirmation, etc.)
  4. Return any HTTP 2xx response
// Example: Laravel webhook handler
Route::post('/webhook/secpaid', function (Request $request) {
    $data = $request->input('data');

    $order = Order::where('secpaid_pay_id', $data['pay_id'])->first();

    if ($order && $data['status'] === 'Success') {
        $order->markAsPaid($data['amount']);
    }

    return response()->json(['received' => true]);
});

Both Together: Complete Flow

Here's how both mechanisms work in a typical integration:

flowchart TB
    subgraph creation [Link Creation]
        A[Your Server] -->|"POST /api/v2/createLink<br/>callback_url + amount"| B[SecPaid API]
        B -->|"pay_link"| A
    end

    subgraph payment [Payment]
        A -->|"Redirect customer"| C[Customer]
        C -->|"Opens pay_link"| D[SecPaid Checkout]
        D -->|"Pays"| E[Payment Provider]
    end

    subgraph notification [Notifications]
        E -->|"Payment confirmed"| F[SecPaid Backend]
        F -->|"POST JSON payload"| G["Your payment_endpoint<br/>(server-to-server)"]
        F -->|"HTTP 302"| H["Customer browser<br/>→ callback_url?status=Success"]
    end
Use Case callback_url payment_endpoint
Show "Thank you" page ✅ Required
Fulfill orders ✅ Required
Send confirmation email ✅ Required
Update UI in real-time ✅ Optional
Complete integration ✅ Set both ✅ Set both

Cancellation Flow

When a customer cancels a cancellable link:

  1. Customer clicks "Cancel" on the checkout page
  2. SecPaid redirects their browser to: callback_url?pay_id=12345&status=cancel
  3. The payment_endpoint is NOT called on cancellation
sequenceDiagram
    participant C as Customer
    participant S as SecPaid Checkout
    participant F as Your Frontend

    C->>S: Clicks "Cancel"
    S->>C: HTTP 302 to callback_url
    C->>F: GET callback_url?pay_id=123&status=cancel
    Note over F: Show "Payment cancelled" page

Triggering Webhooks Manually

If a webhook delivery fails or you need to re-send it, use the sendWebhookViaPayId endpoint:

curl -X POST https://app.secpaid.com/api/v2/sendWebhookViaPayId \
  -H "Content-Type: application/json" \
  -H "token: YOUR_TOKEN" \
  -d '{"pay_ids": "12345,12346", "status": "Success"}'

This re-triggers the payment_endpoint webhook for the specified pay IDs.

Troubleshooting

Problem Cause Solution
Webhook not received payment_endpoint not configured Set it in your account settings
Callback not received Customer closed browser Use payment_endpoint for reliable notifications
Wrong status in callback Customer manipulated URL Never trust callback_url for verification — always use payment_endpoint
Encrypted payload can't be decrypted Wrong encryption key Verify your EncryptionKey attribute in account settings
Webhook received but pay_id unknown Race condition or test data Verify against your order database