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)
Option B: From your account's Callback URLs list
You can configure multiple callback URLs in the platform dashboard:
- Navigate to Settings → Callback URLs
- Add your URLs (e.g.,
https://yoursite.com/payment/done,https://shop.example.com/secpaid/callback) - Click Save Settings
Then reference them by index (0-based) in your API calls:
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:
On cancellation (only if link is cancellable):
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_urlis 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:
Option C: Per-link (index reference)
If you have multiple webhook URLs configured in your account, pass a 0-based index:
Multiple Endpoints¶
You can send webhooks to multiple URLs simultaneously by passing a comma-separated value:
Or mix indices and URLs:
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:
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:
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
paymentEndpointconfigured) —status: "cancel"
It is NOT called on:
- Link deletion
- Refunds (no webhook currently)
Handling the Webhook¶
Your endpoint should:
- Accept a
POSTrequest withContent-Type: application/json - Verify the
pay_idmatches an expected payment - Process the payment (fulfill order, send confirmation, etc.)
- Return any HTTP
2xxresponse
// 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
Recommended Setup¶
| 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:
- Customer clicks "Cancel" on the checkout page
- SecPaid redirects their browser to:
callback_url?pay_id=12345&status=cancel - The
payment_endpointis 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 |