Webhooks

Bidyear supports two webhook directions:

Outbound webhooks

How it works

  1. Create a webhook subscription via POST /api/v1/webhook-subscriptions (requires seller ability).
  2. Save the secret returned at creation — it is shown only once.
  3. When a subscribed event fires, Bidyear sends an HTTPS POST to your URL with a JSON payload.
  4. Verify authenticity using the X-Bidyear-Signature header.
  5. Respond with HTTP 200–299 within 10 seconds. Failed deliveries are logged and retried up to 3 times.

Verifying the signature

Every outbound request includes:

X-Bidyear-Signature: sha256={hex-digest}
X-Bidyear-Event: lot.sold

Compute the expected signature:

// PHP
$expected = 'sha256=' . hash_hmac('sha256', $rawBody, $secret);
if (!hash_equals($expected, $request->header('X-Bidyear-Signature'))) {
    abort(401, 'Invalid signature');
}

// Node.js
const sig = 'sha256=' + crypto.createHmac('sha256', secret)
  .update(rawBody).digest('hex');
if (sig !== req.headers['x-bidyear-signature']) {
  return res.sendStatus(401);
}

Available webhook events

EventFires when
lot.soldA lot hammer is dropped and a winner confirmed
lot.unsoldA lot closes without a winner
auction.publishedAn auction is published/made active
bid.placedA bid is placed on any of your lots
invoice.paidAn invoice is fully paid
invoice.payment_failedA payment attempt on an invoice fails
user.verifiedA user's identity verification is approved
seller.approvedA seller account is approved
subscription.updatedA team subscription status changes

Example outbound payload — lot.sold

POST https://your-server.com/webhook
X-Bidyear-Signature: sha256=abcd1234...
X-Bidyear-Event: lot.sold
Content-Type: application/json

{
  "event": "lot.sold",
  "fired_at": "2026-07-12T14:00:00Z",
  "data": {
    "lot_id": 55,
    "lot_title": "80 Acres — Jones County TX",
    "winner_bid": 42000.00,
    "auction_id": 12
  }
}
GET /api/v1/webhook-subscriptions seller ability

Lists all webhook subscriptions owned by the authenticated user/team.

Response 200

{
  "success": true,
  "data": [
    {
      "id": 1,
      "url": "https://your-server.com/webhook",
      "events": ["lot.sold", "bid.placed"],
      "is_active": true,
      "created_at": "2026-07-01T10:00:00Z"
    }
  ]
}
POST /api/v1/webhook-subscriptions seller ability

Creates a subscription. The response includes a secretcopy it now, it is shown only once.

Request body

{
  "url":    "https://your-server.com/webhook",
  "events": ["lot.sold", "bid.placed"],
  "is_active": true
}

Response 201

{
  "success": true,
  "data": {
    "id": 2,
    "url": "https://your-server.com/webhook",
    "events": ["lot.sold", "bid.placed"],
    "is_active": true,
    "secret": "whs_abcdef1234567890...",
    "created_at": "2026-07-12T09:00:00Z"
  }
}
PATCH /api/v1/webhook-subscriptions/{id} seller ability

Updates url, events, or is_active on an existing subscription.

DELETE /api/v1/webhook-subscriptions/{id} seller ability

Deletes the subscription. No further events will be dispatched to the URL.

GET /api/v1/webhook-subscriptions/{id}/deliveries seller ability

Returns the delivery log for a subscription — useful for debugging failed deliveries.

Response 200 (item)

{
  "id": 99,
  "event": "lot.sold",
  "url": "https://your-server.com/webhook",
  "status_code": 200,
  "success": true,
  "response_body": "OK",
  "attempted_at": "2026-07-12T14:00:01Z",
  "duration_ms": 142
}

Inbound webhooks

These endpoints receive events from external services. They do not require Sanctum authentication — each one uses its own verification mechanism.

POST /api/webhooks/stripe Stripe-Signature

Receives Stripe webhook events. Verified via the Stripe-Signature header using STRIPE_WEBHOOK_SECRET. Handled by StripeWebhookController.

Common handled events

  • payment_intent.succeeded — marks invoice as paid
  • invoice.paid — subscription invoice paid
  • customer.subscription.updated — plan upgrade/downgrade
  • customer.subscription.deleted — subscription cancelled

Response

HTTP 200 — event processed
HTTP 400 — invalid Stripe-Signature header
POST /api/webhooks/bid/{platform} HMAC verified

Receives a bid event from a cross-listing platform (e.g. AuctionTime, Proxibid). The platform slug identifies the platform record in the database. HMAC signature is verified in WebhookController::handleIncomingBid().

Path parameter

ParamDescription
platformPlatform slug (e.g. auctiontime, proxibid)

Response

HTTP 200 — bid recorded
HTTP 401 — invalid HMAC
HTTP 422 — lot not found or auction closed
POST /api/webhooks/sold/{platform} HMAC verified

Receives a lot-sold/hammer event from a cross-listing platform. HMAC verified. Handled by WebhookController::handleIncomingSold().