Altruon Webhooks
Altruon Webhooks notify your server in real time when something happens on your Altruon account: a subscription is created, an invoice is paid, a payment fails, a checkout session completes, and more.
Not to be confused with provider webhooks. The webhooks described in the gateway and billing provider guides are events Altruon receives from your providers. This page covers events Altruon sends to you.
Each webhook is an HTTPS POST with a JSON payload, signed with a per-endpoint secret so you can verify it really came from Altruon.
How it works
- You register an endpoint (an HTTPS URL on your server) in the backoffice and choose which events it should receive.
- Altruon generates a signing secret for the endpoint (shown once — store it securely).
- When a subscribed event occurs, Altruon
POSTs a signed JSON event envelope to your URL. - Your server verifies the signature, responds with a 2xx status, and processes the event asynchronously.
- Failed deliveries are retried automatically with increasing delays, and every attempt is visible in the backoffice delivery log.
Webhook payloads are intentionally thin: they tell you what happened and to which resource. Fetch the full, current state from the Altruon API (e.g. the Transaction Details endpoint) rather than relying on payload contents that may be stale by the time you process the event.
Part 1: Create a webhook endpoint
- Go to Settings > Webhooks.
- Click Add endpoint.
The creation flow has three steps:
Step 1 — Endpoint details
| Field | Required | Description |
|---|---|---|
| Endpoint URL | Yes | Your HTTPS URL, e.g. https://api.your-site.com/webhooks/altruon. Plain http://, credentials in the URL, and private/loopback addresses are rejected. |
| Description | No | A label for your team, e.g. Production billing sync. |
You can click Send test event at this step: Altruon sends an unsigned webhook.ping to the URL to confirm it is reachable and responds with a 2xx before you save.
Step 2 — Select events
Pick the events this endpoint should receive (at least one). Events are grouped by resource (subscription, invoice, payment, …) and searchable. See the full event list below.
Step 3 — Review and get your signing secret
After you confirm, Altruon shows the endpoint's signing secret (format whsec_...).
The secret is displayed only once. Copy it now and store it in your secret manager. Afterwards, the backoffice only shows the last 4 characters.
Good to know: you can have up to 2 active endpoints per site. Use the second one for a staging consumer or during a migration.
Event types
The type field of the payload contains one of the following values:
Subscription
| Event | Sent when |
|---|---|
subscription.created | A subscription is created (including trial signups) |
subscription.activated | A subscription becomes active (e.g. at trial end) |
subscription.paused | A subscription is paused |
subscription.resumed | A paused subscription is resumed |
subscription.cancelled | A subscription is cancelled |
subscription.pre_debit_notification.sent | A pre-debit notification was sent to the customer (e.g. Pix Automático) |
Invoice
| Event | Sent when |
|---|---|
invoice.created | An invoice is generated by the billing platform |
invoice.paid | An invoice is fully paid |
invoice.dunning_started | Payment collection failed and retry (dunning) started |
invoice.dunning_exhausted | All dunning retries failed |
invoice.refunded | An invoice is refunded |
Payment
| Event | Sent when |
|---|---|
payment.processing | A payment is being processed by the gateway |
payment.succeeded | A payment succeeded |
payment.failed | A payment failed |
payment.retry_failed | A recurring payment retry failed |
payment.refunded | A payment is refunded |
Refund
| Event | Sent when |
|---|---|
refund.processing | A refund is being processed |
refund.succeeded | A refund succeeded |
refund.failed | A refund failed |
Customer
| Event | Sent when |
|---|---|
customer.created | A customer is created |
customer.updated | A customer is updated |
Checkout session
| Event | Sent when |
|---|---|
checkout.session.completed | A checkout session completed successfully |
checkout.session.expired | A checkout session expired without payment |
System
| Event | Sent when |
|---|---|
webhook.ping | Test event triggered from the backoffice |
Event payload
Every event is delivered as a JSON envelope:
{
"id": "evt_7f3c2a1b-8d4e-4f6a-9b2c-1e5d7a8f9c0b",
"type": "payment.succeeded",
"created_at": "2026-07-14T13:07:00.000Z",
"domain": "your-tenant.altruon.io",
"livemode": false,
"data": {
"object": {
"resourceType": "PAYMENT",
"source": "gateway",
"providerRef": {
"provider": "PAGBRASIL",
"resourceId": "ORD123456",
"connectionId": "550e8400-e29b-41d4-a716-446655440000"
},
"fetchHint": "Use providerRef.resourceId to query platform or Altruon"
},
"previous_attributes": null,
"truncated": false
}
}
| Field | Type | Description |
|---|---|---|
id | string | Unique event ID (evt_ prefix). Use it to deduplicate: retries of the same event reuse the same ID. |
type | string | One of the event types above. |
created_at | string | ISO 8601 timestamp of when the event occurred. |
domain | string | Your tenant domain. "test" for test events sent from the backoffice. |
livemode | boolean | true for production events, false for sandbox/test. |
data.object | object | The affected resource. resourceType is one of SUBSCRIPTION, PAYMENT, CUSTOMER, INVOICE, REFUND, ORDER. |
data.previous_attributes | object | null | On update events, the fields that changed (previous values). null otherwise. |
data.truncated | boolean | true if the payload exceeded the 256 KB limit and was truncated. |
Verify signatures
Every delivery includes an Altruon-Signature header:
Altruon-Signature: t=1752498420,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd
t— Unix timestamp (seconds) of when the delivery was signed.v1— hex-encoded HMAC-SHA256 of the string{t}.{raw request body}, keyed with your endpoint'swhsec_...secret. During a secret rotation overlap the header can contain twov1entries; the signature is valid if any of them matches.
To verify:
- Parse
tand allv1values from the header. - Reject the request if
tis more than 5 minutes away from the current time (replay protection). - Compute
HMAC-SHA256(secret, "{t}." + rawBody)over the raw request body (before any JSON parsing). - Compare your computed hex digest against each
v1value using a constant-time comparison.
Node.js / Express
const crypto = require('crypto');
const express = require('express');
const app = express();
const TOLERANCE_SECONDS = 300;
function verifyAltruonSignature(rawBody, signatureHeader, secret) {
const elements = (signatureHeader || '').split(',');
const timestamp = elements.find((e) => e.startsWith('t='))?.slice(2);
const signatures = elements.filter((e) => e.startsWith('v1=')).map((e) => e.slice(3));
if (!timestamp || signatures.length === 0) return false;
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > TOLERANCE_SECONDS) return false;
const expected = crypto
.createHmac('sha256', secret)
.update(`${timestamp}.${rawBody}`)
.digest('hex');
return signatures.some((sig) => {
try {
return crypto.timingSafeEqual(Buffer.from(expected, 'hex'), Buffer.from(sig, 'hex'));
} catch {
return false;
}
});
}
// Important: use the RAW body for verification, not the parsed JSON
app.post('/webhooks/altruon', express.raw({ type: 'application/json' }), (req, res) => {
const rawBody = req.body.toString('utf8');
const valid = verifyAltruonSignature(
rawBody,
req.headers['altruon-signature'],
process.env.ALTRUON_WEBHOOK_SECRET
);
if (!valid) return res.status(400).send('Invalid signature');
const event = JSON.parse(rawBody);
// Acknowledge fast, process asynchronously (queue, job, etc.)
// Deduplicate on event.id — retries reuse the same ID.
res.sendStatus(200);
});
Python
import hashlib
import hmac
import time
TOLERANCE_SECONDS = 300
def verify_altruon_signature(raw_body: bytes, signature_header: str, secret: str) -> bool:
elements = (signature_header or "").split(",")
timestamp = next((e[2:] for e in elements if e.startswith("t=")), None)
signatures = [e[3:] for e in elements if e.startswith("v1=")]
if not timestamp or not signatures:
return False
if abs(time.time() - int(timestamp)) > TOLERANCE_SECONDS:
return False
signed_payload = f"{timestamp}.".encode() + raw_body
expected = hmac.new(secret.encode(), signed_payload, hashlib.sha256).hexdigest()
return any(hmac.compare_digest(expected, sig) for sig in signatures)
Test pings sent from Step 1 of the create flow are unsigned (there is no secret yet). Once the endpoint is created, use Send Test on the endpoint detail page to receive a properly signed
webhook.pingand validate your verification code end to end.
Delivery and retries
| Method | POST, Content-Type: application/json |
| User agent | Altruon-Webhook/1.0 |
| Timeout | 10 seconds — respond within this window |
| Success | Any 2xx response status |
| Retries | Failed deliveries are retried with increasing delays (roughly 2h, 12h, then 24h after each failure), up to 4 total attempts |
| Ordering | Deliveries to a given endpoint are sent in order, but retries mean events can arrive out of order — use created_at and fetch current state from the API |
| Idempotency | Retries reuse the same event id — deduplicate on it |
What is retried: timeouts, connection errors, HTTP 5xx, 429, and a few transient 4xx statuses (e.g. 408, 409). Most other 4xx responses (e.g. 401, 400) are treated as permanent failures and are not retried.
Automatic disabling: an endpoint is automatically disabled (and must be manually re-enabled) after 50 consecutive failures, or when its failure rate stays at 95%+ over 3 days. Keep your consumer healthy — respond 2xx quickly and do the heavy lifting asynchronously.
Manage and monitor
The Settings > Webhooks list shows, per endpoint: status (Active/Inactive), subscribed event count, last delivery, and success rate — with aggregate analytics cards on top. Click an endpoint to open its detail page:
- Deliveries — every attempt with event type, status, response code, duration, and the full payload sent. Failed deliveries can be resent manually.
- Events — the endpoint's event subscriptions, editable at any time.
- Settings — URL, description, status, and the signing secret preview (last 4 characters).
You can enable/disable an endpoint from the list at any time; disabled endpoints receive no deliveries.
Best practices
- Verify every request with the signature check above; reject anything invalid with a
400. - Respond fast — return
200immediately and process the event on a queue/worker. Slow handlers hit the 10-second timeout and trigger retries. - Deduplicate on
id— you will occasionally receive the same event more than once. - Fetch, don't trust snapshots — use the event as a trigger and read the current resource state from the Altruon API.
- Don't rely on ordering — a
payment.succeededretry can arrive after a laterinvoice.paid. - Use
livemodeto keep sandbox and production events from crossing environments.
Related docs
- Session API Reference — includes the Transaction Details endpoint for fetching full payment/subscription/invoice state
- Payment gateway integrations — inbound provider webhooks (provider → Altruon)
- Billing provider integrations — inbound billing webhooks (provider → Altruon)