Webhook Events
Receive real-time notifications for verification status changes.
Webhooks
Webhooks allow you to build or set up integrations that subscribe to certain events on Melon. When one of those events is triggered, we'll send a HTTP POST payload to the webhook's configured URL.
This is the recommended way to track verification statuses, rather than polling the API.
Configuring Webhooks
You can configure webhook endpoints directly from the Settings > Integrations tab of your Dashboard. Each endpoint can subscribe to one or more events, and will be assigned a unique Secret for verifying webhook signatures.
Multi-Endpoint Usage (Sandbox vs. Live Environments)
Melon’s webhook architecture is endpoint-based. You can register multiple independent endpoints under your organization—each with its own URL, subscribed events, and signature secret.
This allows you to cleanly separate your local development/testing environment from production:
- Sandbox / Local Testing Endpoint: Register your development URL (e.g., an ngrok or Cloudflare tunnel URL) to receive events during testing.
- Live / Production Endpoint: Register your production HTTPS URL as a separate endpoint subscribed to your production events.
Because each endpoint tracks failures independently, if your local development tunnel drops or becomes unreachable during testing, any consecutive failures will auto-disable only your testing endpoint (consecutiveFailures: 10). Your live production endpoint will remain active and completely unaffected. When you are done testing, you can simply deactivate your sandbox endpoint from the dashboard.
Verifying Endpoints (test.ping)
Before going live or after setting up a new endpoint, you can manually test connectivity directly from the Settings > Integrations tab in your Dashboard by triggering a test event.
This sends a test.ping event to your endpoint with the following sample payload:
{
"id": "evt_test_abcdef1234567890",
"type": "test.ping",
"created": "2024-06-10T14:30:00.000Z",
"data": {
"message": "This is a test webhook from Melon. If you received this, your endpoint is working correctly."
}
}The test.ping request includes the standard x-melon-signature and x-melon-event-id headers, allowing you to verify that both your network routing and HMAC signature verification logic are functioning properly without having to trigger a live verification.
Event Types
Currently, Melon supports the following outbound webhook events:
customer.created- Triggered when a new customer is successfully added to the system for verification.
customer.updated- Triggered when a customer's basic details (phone, email, etc.) are updated.
verification.assigned- Triggered when a physical address verification job is assigned to a Melon field agent.
verification.in_review- Triggered when an agent has submitted their field report, and it is pending internal review.
verification.completed- Triggered when the customer's overall verification process has been successfully finalized and approved.
verification.rejected- Triggered when the verification process fails (e.g., fraudulent document, incorrect address) and is rejected.
address.verified- Triggered when a physical address verification job has been completed.
test.ping- A test event that you can trigger manually from the Melon Dashboard to verify your endpoint's connectivity.
Webhook Payload Structure
All webhook payloads share a common structure:
{
"id": "evt_1234567890abcdef1234567890abcdef",
"type": "verification.completed",
"created": "2024-06-10T14:30:00.000Z",
"data": {
"_id": "60d5ecb8b392...",
"firstName": "Babatunde",
"lastName": "Ogunlesi",
"status": "VERIFIED",
// ... Full customer object
}
}Note: In addition to the
"id"property in the JSON payload body, every webhook request also includes anx-melon-event-idHTTP header containing the same unique event identifier. You can use either value for event deduplication and tracking.
Security & Signatures
To ensure that the webhook was actually sent by Melon and not a malicious third party, every webhook request includes an x-melon-signature header formatted as:
t=1720914000,v1=abcdef123456...The signature is an HMAC SHA-256 hash of the timestamp and JSON request body joined by a period (${timestamp}.${payloadBody}), signed using your endpoint's Webhook Secret.
Verifying the Signature
Here is an example of how to parse the x-melon-signature header and verify the webhook signature across different languages:
const crypto = require('crypto');
function verifySignature(payloadBody, signatureHeader, webhookSecret) {
const parts = signatureHeader.split(',').reduce((acc, item) => {
const [key, value] = item.split('=');
acc[key] = value;
return acc;
}, {});
if (!parts.t || !parts.v1) return false;
const signaturePayload = `${parts.t}.${payloadBody}`;
const computedSignature = crypto
.createHmac('sha256', webhookSecret)
.update(signaturePayload)
.digest('hex');
return computedSignature === parts.v1;
}import hmac
import hashlib
def verify_signature(payload_body, signature_header, webhook_secret):
parts = dict(item.split('=', 1) for item in signature_header.split(',') if '=' in item)
if 't' not in parts or 'v1' not in parts:
return False
signature_payload = f"{parts['t']}.{payload_body}"
computed_signature = hmac.new(
webhook_secret.encode('utf-8'),
signature_payload.encode('utf-8'),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(computed_signature, parts['v1'])package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"fmt"
"strings"
)
func VerifySignature(payloadBody []byte, signatureHeader string, webhookSecret string) bool {
parts := make(map[string]string)
for _, item := range strings.Split(signatureHeader, ",") {
kv := strings.SplitN(item, "=", 2)
if len(kv) == 2 {
parts[kv[0]] = kv[1]
}
}
timestamp, okT := parts["t"]
signature, okSig := parts["v1"]
if !okT || !okSig {
return false
}
signaturePayload := fmt.Sprintf("%s.%s", timestamp, string(payloadBody))
h := hmac.New(sha256.New, []byte(webhookSecret))
h.Write([]byte(signaturePayload))
computedSignature := hex.EncodeToString(h.Sum(nil))
return computedSignature == signature
}<?php
function verifySignature($payloadBody, $signatureHeader, $webhookSecret) {
$parts = [];
foreach (explode(',', $signatureHeader) as $item) {
$kv = explode('=', $item, 2);
if (count($kv) === 2) {
$parts[$kv[0]] = $kv[1];
}
}
if (!isset($parts['t'], $parts['v1'])) {
return false;
}
$signaturePayload = $parts['t'] . '.' . $payloadBody;
$computedSignature = hash_hmac('sha256', $signaturePayload, $webhookSecret);
return hash_equals($computedSignature, $parts['v1']);
}
?>If the computed signature matches v1 in the x-melon-signature header, the webhook is legitimate.
Retries and Failures
If your server responds with a status code outside the 2xx range (e.g., 404, 500), or if the request times out (exceeds 10 seconds), Melon will assume the webhook delivery failed.
We utilize an exponential backoff strategy for retrying failed webhooks. If the initial delivery fails, Melon will attempt to resend the event up to 4 more times, with the following delays:
- 1st retry: 1 minute after initial failure
- 2nd retry: 5 minutes after previous attempt
- 3rd retry: 30 minutes after previous attempt
- 4th retry: 2 hours after previous attempt
Ensure your webhook handler acknowledges receipt quickly (by returning a 200 OK) before performing any long-running processing tasks.
Endpoint Health and Auto-Disable
Melon actively monitors the health of your webhook endpoints. If an endpoint fails to acknowledge 10 consecutive webhook deliveries across multiple events or retries, it will be automatically disabled to prevent unnecessary load.
When an endpoint is disabled, it will stop receiving new events. You can review the failure logs and re-enable the endpoint directly from your Dashboard under Settings > Integrations once the issue on your server is resolved.
Delivery History and Replays
You can view the delivery history of your webhook endpoints in the Settings > Integrations tab of your Dashboard.
The dashboard provides a detailed log of all events sent to your endpoints, including the delivery status, HTTP response codes, and attempt counts. If an event fails to deliver after all automated retries, you can manually replay the event from the delivery history panel. You can also send a Test Ping to any endpoint to verify your integration is functioning correctly.