Overview
Webhooks allow external systems to receive real-time notifications when events occur in Auction Excellence. Instead of polling the API for changes, configure webhook endpoints to receive HTTP POST requests with event data.
Real-time Instant notifications when events occur
Secure HMAC signature verification for authenticity
Reliable Automatic retries with exponential backoff
Flexible Filter events by type and auction
How Webhooks Work
Event Occurs
An action happens in Auction Excellence (e.g., submission created)
Webhook Triggered
A database trigger fires and queues the webhook delivery
HTTP POST Sent
Your endpoint receives a POST request with event data
Acknowledgment
Return a 2xx status code to confirm receipt
Available Events
Submission Events
Event Trigger Description submission.createdNew lot submission Fired when a team member submits a lot count submission.updatedSubmission modified Fired when a submission is edited (rare)
Payload Example:
{
"event" : "submission.created" ,
"timestamp" : "2025-01-15T10:30:00Z" ,
"auction_id" : "auction-uuid" ,
"data" : {
"id" : "submission-uuid" ,
"location_id" : "location-uuid" ,
"location_name" : "North Lot" ,
"user_id" : "user-uuid" ,
"submitted_by" : "John Smith" ,
"submitted_at" : "2025-01-15T10:30:00Z" ,
"entries" : [
{
"type_id" : "type-uuid" ,
"type_name" : "Dealer" ,
"car_count" : 45 ,
"image_url" : "https://..."
},
{
"type_id" : "type-uuid" ,
"type_name" : "Retail" ,
"car_count" : 30 ,
"image_url" : null
}
],
"total_car_count" : 75 ,
"image_url" : "https://..."
}
}
Inspection Events
Event Trigger Description inspection.createdNew inspection Fired when a quality inspection is submitted inspection.defect_addedDefect recorded Fired when a defect is added to an inspection
Payload Example:
{
"event" : "inspection.created" ,
"timestamp" : "2025-01-15T10:30:00Z" ,
"auction_id" : "auction-uuid" ,
"data" : {
"id" : "inspection-uuid" ,
"location_id" : "location-uuid" ,
"location_name" : "North Lot" ,
"user_id" : "user-uuid" ,
"inspector_name" : "Jane Inspector" ,
"vin_number" : "1HGBH41JXMN109186" ,
"barcode" : "A12345" ,
"submitted_at" : "2025-01-15T10:30:00Z" ,
"defect_count" : 3 ,
"defects" : [
{
"category" : "Body Damage" ,
"notes" : "Scratch on driver door"
}
]
}
}
Rock Report Events
Event Trigger Description problem.createdNew rock report Fired when a rock report is submitted problem.status_changedStatus updated Fired when status changes (open → in_progress → closed) problem.assignedAssignment changed Fired when rock is assigned/reassigned problem.action_completedAction marked complete Fired when an action item is completed
Payload Example:
{
"event" : "problem.status_changed" ,
"timestamp" : "2025-01-15T10:30:00Z" ,
"auction_id" : "auction-uuid" ,
"data" : {
"id" : "problem-uuid" ,
"issue_name" : "Delayed Vehicle Processing" ,
"area" : "Intake" ,
"location_id" : "location-uuid" ,
"location_name" : "North Lot" ,
"previous_status" : "open" ,
"new_status" : "in_progress" ,
"prepared_by" : {
"id" : "user-uuid" ,
"name" : "John Smith"
},
"assigned_to" : {
"id" : "user-uuid" ,
"name" : "Jane Manager"
},
"action_count" : 5 ,
"completed_action_count" : 2 ,
"updated_at" : "2025-01-15T10:30:00Z"
}
}
Chat Events
Event Trigger Description message.createdNew message sent Fired when a message is sent in any channel message.editedMessage edited Fired when a message content is modified message.deletedMessage deleted Fired when a message is soft-deleted channel.createdNew channel Fired when a channel is created channel.archivedChannel archived Fired when a channel is archived
Payload Example:
{
"event" : "message.created" ,
"timestamp" : "2025-01-15T10:30:00Z" ,
"auction_id" : "auction-uuid" ,
"data" : {
"id" : "message-uuid" ,
"channel_id" : "channel-uuid" ,
"channel_name" : "general" ,
"user_id" : "user-uuid" ,
"sender_name" : "John Smith" ,
"content" : "Hello team!" ,
"message_type" : "text" ,
"is_thread_reply" : false ,
"parent_message_id" : null ,
"mentions" : [],
"created_at" : "2025-01-15T10:30:00Z"
}
}
Member Events
Event Trigger Description member.invitedInvitation sent Fired when a user is invited to an auction member.joinedMember joined Fired when an invitation is accepted member.role_changedRole updated Fired when a member’s role changes member.removedMember removed Fired when a member is removed
Payload Example:
{
"event" : "member.joined" ,
"timestamp" : "2025-01-15T10:30:00Z" ,
"auction_id" : "auction-uuid" ,
"data" : {
"user_id" : "user-uuid" ,
"email" : "newmember@example.com" ,
"full_name" : "New Member" ,
"role" : "team_member" ,
"invited_by" : {
"id" : "inviter-uuid" ,
"name" : "Admin User"
},
"joined_at" : "2025-01-15T10:30:00Z"
}
}
Webhook Payload Structure
All webhook payloads follow a consistent structure:
{
"event" : "event.type" ,
"timestamp" : "2025-01-15T10:30:00Z" ,
"webhook_id" : "delivery-uuid" ,
"auction_id" : "auction-uuid" ,
"data" : {
// Event-specific data
}
}
Common Fields
Field Type Description eventstring Event type identifier timestampISO 8601 When the event occurred webhook_idUUID Unique ID for this delivery (for deduplication) auction_idUUID The auction where the event occurred dataobject Event-specific payload
Configuring Webhooks
Webhook configuration is available to auction administrators through the admin dashboard or via API.
Via Admin Dashboard
Navigate to Settings
Go to Settings → Integrations → Webhooks
Add Endpoint
Click Add Webhook and enter your endpoint URL
Select Events
Choose which events should trigger this webhook
Save and Test
Save the configuration and use the Test button to verify
Via API
curl -X POST 'https://your-project.supabase.co/rest/v1/webhook_endpoints' \
-H "apikey: YOUR_ANON_KEY" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"auction_id": "auction-uuid",
"url": "https://your-server.com/webhooks/ae",
"events": ["submission.created", "inspection.created"],
"secret": "your-signing-secret",
"is_active": true
}'
Security
Signature Verification
All webhook requests include an HMAC-SHA256 signature in the X-Webhook-Signature header. Always verify this signature before processing the payload.
Header Format:
X-Webhook-Signature: sha256=5d7d...abc123
Verification Code
import crypto from 'crypto'
function verifyWebhookSignature (
payload : string ,
signature : string ,
secret : string
) : boolean {
const expectedSignature = crypto
. createHmac ( 'sha256' , secret )
. update ( payload )
. digest ( 'hex' )
const providedSignature = signature . replace ( 'sha256=' , '' )
return crypto . timingSafeEqual (
Buffer . from ( expectedSignature ),
Buffer . from ( providedSignature )
)
}
// Express.js example
app . post ( '/webhooks/ae' , express . raw ({ type: 'application/json' }), ( req , res ) => {
const signature = req . headers [ 'x-webhook-signature' ]
const payload = req . body . toString ()
if ( ! verifyWebhookSignature ( payload , signature , process . env . WEBHOOK_SECRET )) {
return res . status ( 401 ). send ( 'Invalid signature' )
}
const event = JSON . parse ( payload )
console . log ( 'Received event:' , event . event )
// Process the event...
res . status ( 200 ). send ( 'OK' )
})
import hmac
import hashlib
from flask import Flask, request
def verify_webhook_signature ( payload : bytes , signature : str , secret : str ) -> bool :
expected = hmac.new(
secret.encode(),
payload,
hashlib.sha256
).hexdigest()
provided = signature.replace( 'sha256=' , '' )
return hmac.compare_digest(expected, provided)
@app.route ( '/webhooks/ae' , methods = [ 'POST' ])
def handle_webhook ():
signature = request.headers.get( 'X-Webhook-Signature' )
payload = request.get_data()
if not verify_webhook_signature(payload, signature, WEBHOOK_SECRET ):
return 'Invalid signature' , 401
event = request.get_json()
print ( f "Received event: { event[ 'event' ] } " )
# Process the event...
return 'OK' , 200
package main
import (
" crypto/hmac "
" crypto/sha256 "
" encoding/hex "
" io "
" net/http "
" strings "
)
func verifyWebhookSignature ( payload [] byte , signature , secret string ) bool {
mac := hmac . New ( sha256 . New , [] byte ( secret ))
mac . Write ( payload )
expected := hex . EncodeToString ( mac . Sum ( nil ))
provided := strings . TrimPrefix ( signature , "sha256=" )
return hmac . Equal ([] byte ( expected ), [] byte ( provided ))
}
func webhookHandler ( w http . ResponseWriter , r * http . Request ) {
signature := r . Header . Get ( "X-Webhook-Signature" )
payload , _ := io . ReadAll ( r . Body )
if ! verifyWebhookSignature ( payload , signature , webhookSecret ) {
http . Error ( w , "Invalid signature" , http . StatusUnauthorized )
return
}
// Process the event...
w . WriteHeader ( http . StatusOK )
}
Header Description X-Webhook-IDUnique ID for this delivery X-Webhook-TimestampUnix timestamp when sent X-Webhook-EventEvent type (e.g., submission.created) Content-TypeAlways application/json User-AgentAuctionExcellence-Webhook/1.0
Handling Webhooks
Response Requirements
Return a 2xx status code (200-299) to acknowledge receipt
Response must be returned within 30 seconds
Response body is ignored
Retry Policy
If your endpoint doesn’t respond with a 2xx status:
Attempt Delay 1 Immediate 2 1 minute 3 5 minutes 4 30 minutes 5 2 hours 6 12 hours
After 6 failed attempts, the webhook is marked as failed and no more retries occur.
Repeated failures may result in automatic endpoint disabling. Monitor your endpoint health.
Idempotency
Webhooks may be delivered more than once due to retries. Use the webhook_id field to detect and handle duplicates:
const processedWebhooks = new Set < string >()
function handleWebhook ( event : WebhookEvent ) {
if ( processedWebhooks . has ( event . webhook_id )) {
console . log ( 'Duplicate webhook, skipping' )
return
}
processedWebhooks . add ( event . webhook_id )
// Process the event...
}
Testing Webhooks
Test Endpoint
Send a test webhook to verify your endpoint configuration:
curl -X POST 'https://your-project.supabase.co/rest/v1/rpc/test_webhook' \
-H "apikey: YOUR_ANON_KEY" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"endpoint_id": "endpoint-uuid",
"event_type": "submission.created"
}'
Local Development
Use a tunneling service like ngrok for local testing:
# Start ngrok tunnel
ngrok http 3000
# Use the generated URL as your webhook endpoint
# https://abc123.ngrok.io/webhooks/ae
Webhook Logs
View recent webhook deliveries in the admin dashboard:
Settings → Integrations → Webhooks → Delivery History
Each log entry shows:
Event type
Delivery status (success/failed)
Response status code
Response time
Error message (if failed)
Best Practices
Never process a webhook without verifying the HMAC signature. This prevents forged requests.
Return a 200 response immediately, then process the event asynchronously. This prevents timeouts. app . post ( '/webhooks/ae' , async ( req , res ) => {
res . status ( 200 ). send ( 'OK' ) // Respond immediately
// Process asynchronously
setImmediate (() => processEvent ( req . body ))
})
Store the webhook_id and check for duplicates before processing.
For high-volume scenarios, push webhooks to a queue (Redis, SQS, etc.) and process separately.
Set up alerts for webhook delivery failures to catch integration issues early.
Store webhook secrets in environment variables, not in code.
Troubleshooting
Webhooks not being received
Verify your endpoint URL is correct and publicly accessible
Check that the endpoint is enabled in webhook settings
Ensure the event types you need are selected
Review the delivery history for error messages
Signature verification failing
Ensure you’re using the raw request body, not parsed JSON
Verify you’re using the correct webhook secret
Check that the signature header name is correct (X-Webhook-Signature)
Receiving duplicate events
Implement idempotency using the webhook_id
Check if your endpoint is responding slowly (causing retries)
Ensure you’re returning 2xx status codes
Endpoint disabled automatically
Endpoints are disabled after repeated failures. Fix the issue, then:
Go to Settings → Webhooks
Find the disabled endpoint
Click Enable and save
Send a test webhook to verify
Next Steps