Webhooks
Webhooks deliver real-time notifications when events occur in an Martis account. Instead of polling the API for updates, webhooks push event data to a configured endpoint, enabling immediate response to payment completions, payout status changes, and other critical events.
How Webhooks Work
When an event occurs (e.g., a payment succeeds), Martis sends an HTTP POST request to the configured webhook endpoint with event details in the request body. The receiving server processes the event and returns a 2xx status code to acknowledge receipt.
| Step | Description |
|---|---|
| 1. Event occurs | A payment charge succeeds, a payout completes, etc. |
| 2. Webhook sent | Martis sends a POST request to the registered endpoint |
| 3. Signature verification | The receiving server verifies the request signature |
| 4. Event processing | The server processes the event data |
| 5. Acknowledgment | The server returns a 2xx response |
Configure Webhook Endpoints
Create an Endpoint
Webhook endpoints must meet the following requirements:
- HTTPS — Only secure endpoints are supported
- POST method — Endpoints must accept POST requests
- JSON content — Request bodies are JSON-formatted
- 2xx response — Return a success status code to acknowledge receipt
Register in Dashboard
-
Navigate to Creator Hub → Integration → Webhooks

-
Click Add Webhook Destination
-
Enter the HTTPS endpoint URL and optional description
-
Save the configuration
A webhook secret is generated upon creation. Store this secret securely — it is required for signature verification.
Manage Endpoints
Registered endpoints can be viewed, updated, or deleted from the dashboard. Each endpoint displays delivery statistics and recent event history.
Signature Verification
Verify webhook signatures to ensure requests originate from Martis and have not been tampered with.
Signature Components
Each webhook request includes two headers for verification:
| Header | Description |
|---|---|
{HEADER_TIMESTAMP} | Unix timestamp when the webhook was sent |
{HEADER_SIGNATURE} | HMAC-SHA256 signature of the request |
Verification Process
- Extract the timestamp and signature from request headers
- Construct the signed payload:
{timestamp}.{raw_body} - Compute HMAC-SHA256 using the webhook secret
- Compare the computed signature with the received signature
The request body must be used exactly as received (raw bytes). Do not parse, reformat, or modify the body before computing the signature.

Implementation Examples
Signature Verification
using System.Security.Cryptography;
using System.Text;
public static class WebhookVerification
{
public static bool VerifySignature(
string rawBody,
string timestamp,
string receivedSignature,
string webhookSecret)
{
var payload = $"{timestamp}.{rawBody}";
var expectedSignature = ComputeHmacSha256(payload, webhookSecret);
return CryptographicOperations.FixedTimeEquals(
Encoding.UTF8.GetBytes(expectedSignature),
Encoding.UTF8.GetBytes(receivedSignature)
);
}
private static string ComputeHmacSha256(string data, string key)
{
using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(key));
var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(data));
return Convert.ToHexString(hash).ToLower();
}
}
Event Types
Payment Charge Events
Events triggered by payment charge status changes:
| Event | Description |
|---|---|
payment_charge.created | A new payment charge was created |
payment_charge.update | Payment charge details were updated |
payment_charge.succeeded | Payment was completed successfully |
payment_charge.failed | Payment failed or expired |
Payment Charge Payload Example
payment_charge.succeeded
{
"id": "evt_01HZEVT123456ABCDEF",
"type": "payment_charge.succeeded",
"created_at": "2025-01-15T11:15:00Z",
"data": {
"id": "01HZCHARGE123456ABCDEF",
"payment_method": "qris",
"channel": "gudang_voucher",
"status": "success",
"currency": "idr",
"amount": 50000.00,
"fee": 350.00,
"client_reference_id": "ORDER-001",
"customer_details": {
"name": "John Doe",
"email": "john@example.com",
"phone_number": "081234567890"
},
"items": [
{
"name": "Product A",
"price": 50000.00,
"quantity": 1
}
],
"description": "Payment for Order #001",
"expires_at": "2025-01-15T11:30:00Z",
"paid_at": "2025-01-15T11:15:00Z",
"created_at": "2025-01-15T11:00:00Z",
"updated_at": "2025-01-15T11:15:00Z"
}
}
Payout Events
Events triggered by payout status changes:
| Event | Description |
|---|---|
payout.created | A new payout was created |
payout.updated | Payout details were updated |
payout.pending | Payout is awaiting processing |
payout.processing | Payout is being processed |
payout.succeeded | Payout was completed successfully |
payout.failed | Payout failed |
payout.suspected | Payout was flagged for review |
payout.rejected | Payout was rejected after review |
payout.refunded | Payout was refunded to source account |
Payout Payload Example
payout.succeeded
{
"id": "evt_01HZEVT789012GHIJKL",
"type": "payout.succeeded",
"created_at": "2025-01-15T14:30:00Z",
"data": {
"id": "01HZPAYOUT123456ABCDEF",
"account_id": "01HZABC987654321FEDCBA",
"currency": "idr",
"status": "completed",
"type": "bank_account",
"bank": {
"code": "bca",
"name": "Bank Central Asia",
"country_code": "ID"
},
"account_number": "1234567890",
"account_holder_name": "John Doe",
"amount": 500000.00,
"fee": 5000.00,
"description": "Vendor payment",
"client_reference_id": "PO-2024-0001",
"created_at": "2025-01-15T14:00:00Z",
"updated_at": "2025-01-15T14:30:00Z"
}
}
Retry Mechanism
When a webhook delivery fails, Martis automatically retries the request using exponential backoff.
Retry Policy
| Attempt | Delay | Cumulative Time |
|---|---|---|
| 1 | Immediate | 0 |
| 2 | 5 minutes | 5 minutes |
| 3 | 30 minutes | 35 minutes |
Failure Conditions
A delivery is considered failed when:
- The endpoint returns a non-2xx status code
- The request times out (30 seconds)
- The endpoint is unreachable
After all retry attempts are exhausted, the webhook delivery is marked as failed. Failed events can be viewed in the dashboard and manually retried.
Best Practices
Endpoint Implementation
- Return quickly — Acknowledge receipt immediately, then process asynchronously
- Handle duplicates — Use event IDs to implement idempotent processing
- Verify signatures — Always validate the signature before processing
- Log events — Maintain logs for debugging and auditing
Error Handling
- Return appropriate status codes — Use
2xxfor success,4xxfor client errors,5xxfor server errors - Implement retry logic — Design handlers to safely process retried events
- Monitor failures — Set up alerts for failed webhook deliveries
Security
- Use HTTPS — Never expose HTTP endpoints for webhooks
- Validate signatures — Reject requests with invalid or missing signatures
- Verify timestamps — Reject requests with stale timestamps (e.g., older than 5 minutes)
- Restrict access — Only allow traffic from Martis IP ranges if possible
Testing Webhooks
Sandbox Testing
Use the sandbox environment to test webhook integration:
- Configure a webhook endpoint in the sandbox dashboard
- Create test payment charges or payouts
- Verify webhook delivery and signature validation
- Test error handling and retry scenarios
Local Development
For local development, use a tunneling service to expose a local endpoint:
# Example using ngrok
ngrok http 3000
# Use the generated HTTPS URL as the webhook endpoint
# https://abc123.ngrok.io/webhook
Troubleshooting
| Issue | Solution |
|---|---|
| Webhooks not received | Verify endpoint URL is correct and accessible |
| Signature verification fails | Ensure raw body is used without modification |
| Duplicate events received | Implement idempotent processing using event IDs |
| Timeouts | Reduce processing time; acknowledge quickly and process async |
| SSL errors | Verify SSL certificate is valid and properly configured |