Developers

Webhooks

Signed HTTPS notifications for orders, messages, disputes and stock.

On this page
  1. Set up an endpoint
  2. Events
  3. The request
  4. Verify the signature
  5. Responses, retries and auto-disable
  6. URL rules (SSRF protection)
  7. Best practices

Instead of asking the API every minute whether something changed, let MythicDeals tell you. When an event happens in your store we send a signed POST with a JSON body to your HTTPS endpoint, and retry for about 20 h 36 min if your server is down.

Set up an endpoint

  1. Add the URL. On Settings → Webhooks, or with POST /webhooks (scope webhooks:write). Pick the events you want. You can have up to 10 endpoints.
  2. Store the secret. The signing secret (whsec_…) is shown only once, when the endpoint is created or its secret is rotated. Keep it on your server only.
  3. Send a test. “Send test” (or POST /webhooks/{webhook}/test) delivers a signed ping right away and shows your server’s answer.

Events

Endpoints belong to a seller and only receive events about that seller’s store: their orders, the buyer messages in those orders, disputes on them and their listings. Buyers never receive webhooks. Every event below is sent once per subscribed endpoint.

EventWhen it firesWho receives it
order.created A buyer placed an order for one of your listings. It is not paid yet — do not deliver. The seller of the order / listing
order.paid Payment is confirmed and held in escrow. Deliver before delivery_due_at. The seller of the order / listing
order.processing The order was marked as in progress (by you, on the website or via the API). The seller of the order / listing
order.delivered The order was delivered — manually, via the API or by instant delivery. The seller of the order / listing
order.completed The buyer confirmed, or the order completed automatically at auto_release_at. The seller of the order / listing
order.disputed The buyer opened a dispute or a warranty claim. The money is frozen. The seller of the order / listing
order.refunded The order was refunded to the buyer. The seller of the order / listing
order.cancelled The order was cancelled. The seller of the order / listing
order.expired The buyer did not pay in time; the order expired and the stock is released. The seller of the order / listing
message.created The buyer sent a message in an order chat. Your own messages and system messages are not sent. The seller of the order / listing
dispute.created A dispute or a warranty claim was opened on one of your orders (usually by the buyer). Sent once per dispute, followed by a dispute.updated for the same change. Fetch the case with GET /disputes/{id}. The seller of the order / listing
dispute.updated A dispute on one of your orders was opened or changed status. One event per dispute change. The seller of the order / listing
listing.sold_out An order took the last unit of stock of a listing; it is now sold_out. The seller of the order / listing
ping Sent only by the “Send test” button or POST /webhooks/{webhook}/test. Always delivered, whatever events you chose. The endpoint being tested

All order.* events carry the full order as the seller sees it (same fields as GET /orders/{order}) plus previous_status (except order.created).

dispute.created fires once, when a dispute or a warranty claim is opened on one of your orders; dispute.updated fires for that same change and for every later status change (your counter-offer, an escalation, the resolution). Both carry the same data: the dispute in exactly the shape GET /disputes returns (requested_outcome, requested_amount_cents only for partial_refund, awaiting, pending_offer…) and a short order. Read the full case — offers, evidence, timeline — with GET /disputes/{id} (scope disputes:read) and answer with the dispute endpoints. Subscribe to dispute.created alone if you only need to know when a new case needs you.

Example payloads:

order.created
order.created
{
  "id": "evt_01m5weqw0gxp9kz6h3hxcjf76s",
  "type": "order.created",
  "created_at": "2026-10-26T15:54:50Z",
  "data": {
    "order": {
      "id": 2,
      "number": "48292852",
      "status": "pending_payment",
      "listing": {
        "id": 1,
        "title": "Immortal 2 account, 42 skins",
        "type": "account",
        "game": "Valorant"
      },
      "quantity": 1,
      "unit_price_cents": 9999,
      "subtotal_cents": 9999,
      "buyer_fee_cents": 0,
      "discount_cents": 0,
      "total_paid_cents": 9999,
      "platform_fee_cents": 999,
      "fee_percent": 10,
      "seller_amount_cents": 9000,
      "currency": "USD",
      "buyer": {
        "id": 2,
        "name": "Rina"
      },
      "buyer_note": null,
      "topup_fields": null,
      "delivery": null,
      "dispute": null,
      "payment_due_at": "2026-10-26T16:54:50Z",
      "paid_at": null,
      "delivery_due_at": null,
      "delivered_at": null,
      "auto_release_at": null,
      "completed_at": null,
      "warranty_ends_at": null,
      "created_at": "2026-10-26T15:54:50Z",
      "updated_at": "2026-10-26T15:54:50Z"
    }
  }
}
order.paid
order.paid
{
  "id": "evt_01m5weqw10bhte2s9tvxk5kqzf",
  "type": "order.paid",
  "created_at": "2026-10-26T15:54:50Z",
  "data": {
    "order": {
      "id": 2,
      "number": "48292852",
      "status": "paid",
      "listing": {
        "id": 1,
        "title": "Immortal 2 account, 42 skins",
        "type": "account",
        "game": "Valorant"
      },
      "quantity": 1,
      "unit_price_cents": 9999,
      "subtotal_cents": 9999,
      "buyer_fee_cents": 0,
      "discount_cents": 0,
      "total_paid_cents": 9999,
      "platform_fee_cents": 999,
      "fee_percent": 10,
      "seller_amount_cents": 9000,
      "currency": "USD",
      "buyer": {
        "id": 2,
        "name": "Rina"
      },
      "buyer_note": null,
      "topup_fields": null,
      "delivery": null,
      "dispute": null,
      "payment_due_at": "2026-10-26T16:54:50Z",
      "paid_at": "2026-10-26T15:54:50Z",
      "delivery_due_at": "2026-10-26T16:24:50Z",
      "delivered_at": null,
      "auto_release_at": null,
      "completed_at": null,
      "warranty_ends_at": null,
      "created_at": "2026-10-26T15:54:50Z",
      "updated_at": "2026-10-26T15:54:50Z"
    },
    "previous_status": "pending_payment"
  }
}
order.processing
order.processing
{
  "id": "evt_01m5wf3fae3f86132407a1e735",
  "type": "order.processing",
  "created_at": "2026-10-26T15:54:50Z",
  "data": {
    "order": {
      "id": 2,
      "number": "48292852",
      "status": "processing",
      "listing": {
        "id": 1,
        "title": "Immortal 2 account, 42 skins",
        "type": "account",
        "game": "Valorant"
      },
      "quantity": 1,
      "unit_price_cents": 9999,
      "subtotal_cents": 9999,
      "buyer_fee_cents": 0,
      "discount_cents": 0,
      "total_paid_cents": 9999,
      "platform_fee_cents": 999,
      "fee_percent": 10,
      "seller_amount_cents": 9000,
      "currency": "USD",
      "buyer": {
        "id": 2,
        "name": "Rina"
      },
      "buyer_note": null,
      "topup_fields": null,
      "delivery": null,
      "dispute": null,
      "payment_due_at": "2026-10-26T16:54:50Z",
      "paid_at": "2026-10-26T15:54:50Z",
      "delivery_due_at": "2026-10-26T16:24:50Z",
      "delivered_at": null,
      "auto_release_at": null,
      "completed_at": null,
      "warranty_ends_at": null,
      "created_at": "2026-10-26T15:54:50Z",
      "updated_at": "2026-10-26T15:54:50Z"
    },
    "previous_status": "paid"
  }
}
order.delivered
order.delivered
{
  "id": "evt_01m5wfc4aa3a27c01f65f1a8ad",
  "type": "order.delivered",
  "created_at": "2026-10-26T15:54:50Z",
  "data": {
    "order": {
      "id": 2,
      "number": "48292852",
      "status": "delivered",
      "listing": {
        "id": 1,
        "title": "Immortal 2 account, 42 skins",
        "type": "account",
        "game": "Valorant"
      },
      "quantity": 1,
      "unit_price_cents": 9999,
      "subtotal_cents": 9999,
      "buyer_fee_cents": 0,
      "discount_cents": 0,
      "total_paid_cents": 9999,
      "platform_fee_cents": 999,
      "fee_percent": 10,
      "seller_amount_cents": 9000,
      "currency": "USD",
      "buyer": {
        "id": 2,
        "name": "Rina"
      },
      "buyer_note": null,
      "topup_fields": null,
      "delivery": {
        "delivered_at": "2026-10-26T16:02:10Z",
        "has_credentials": true,
        "proof_count": 0
      },
      "dispute": null,
      "payment_due_at": "2026-10-26T16:54:50Z",
      "paid_at": "2026-10-26T15:54:50Z",
      "delivery_due_at": "2026-10-26T16:24:50Z",
      "delivered_at": "2026-10-26T16:02:10Z",
      "auto_release_at": "2026-10-29T16:02:10Z",
      "completed_at": null,
      "warranty_ends_at": null,
      "created_at": "2026-10-26T15:54:50Z",
      "updated_at": "2026-10-26T15:54:50Z"
    },
    "previous_status": "processing"
  }
}
order.completed
order.completed
{
  "id": "evt_01m5wf9dad7c7bae197a7b4b59",
  "type": "order.completed",
  "created_at": "2026-10-26T15:54:50Z",
  "data": {
    "order": {
      "id": 2,
      "number": "48292852",
      "status": "completed",
      "listing": {
        "id": 1,
        "title": "Immortal 2 account, 42 skins",
        "type": "account",
        "game": "Valorant"
      },
      "quantity": 1,
      "unit_price_cents": 9999,
      "subtotal_cents": 9999,
      "buyer_fee_cents": 0,
      "discount_cents": 0,
      "total_paid_cents": 9999,
      "platform_fee_cents": 999,
      "fee_percent": 10,
      "seller_amount_cents": 9000,
      "currency": "USD",
      "buyer": {
        "id": 2,
        "name": "Rina"
      },
      "buyer_note": null,
      "topup_fields": null,
      "delivery": {
        "delivered_at": "2026-10-26T16:02:10Z",
        "has_credentials": true,
        "proof_count": 0
      },
      "dispute": null,
      "payment_due_at": "2026-10-26T16:54:50Z",
      "paid_at": "2026-10-26T15:54:50Z",
      "delivery_due_at": "2026-10-26T16:24:50Z",
      "delivered_at": "2026-10-26T16:02:10Z",
      "auto_release_at": "2026-10-29T16:02:10Z",
      "completed_at": "2026-10-27T09:15:00Z",
      "warranty_ends_at": null,
      "created_at": "2026-10-26T15:54:50Z",
      "updated_at": "2026-10-26T15:54:50Z"
    },
    "previous_status": "delivered"
  }
}
order.disputed
order.disputed
{
  "id": "evt_01m5wfbfdbbc287846bfda92a7",
  "type": "order.disputed",
  "created_at": "2026-10-26T15:54:50Z",
  "data": {
    "order": {
      "id": 2,
      "number": "48292852",
      "status": "disputed",
      "listing": {
        "id": 1,
        "title": "Immortal 2 account, 42 skins",
        "type": "account",
        "game": "Valorant"
      },
      "quantity": 1,
      "unit_price_cents": 9999,
      "subtotal_cents": 9999,
      "buyer_fee_cents": 0,
      "discount_cents": 0,
      "total_paid_cents": 9999,
      "platform_fee_cents": 999,
      "fee_percent": 10,
      "seller_amount_cents": 9000,
      "currency": "USD",
      "buyer": {
        "id": 2,
        "name": "Rina"
      },
      "buyer_note": null,
      "topup_fields": null,
      "delivery": {
        "delivered_at": "2026-10-26T16:02:10Z",
        "has_credentials": true,
        "proof_count": 0
      },
      "dispute": null,
      "payment_due_at": "2026-10-26T16:54:50Z",
      "paid_at": "2026-10-26T15:54:50Z",
      "delivery_due_at": "2026-10-26T16:24:50Z",
      "delivered_at": "2026-10-26T16:02:10Z",
      "auto_release_at": "2026-10-29T16:02:10Z",
      "completed_at": null,
      "warranty_ends_at": null,
      "created_at": "2026-10-26T15:54:50Z",
      "updated_at": "2026-10-26T15:54:50Z"
    },
    "previous_status": "delivered"
  }
}
order.refunded
order.refunded
{
  "id": "evt_01m5wf91cac38c8cc6e9aa140a",
  "type": "order.refunded",
  "created_at": "2026-10-26T15:54:50Z",
  "data": {
    "order": {
      "id": 2,
      "number": "48292852",
      "status": "refunded",
      "listing": {
        "id": 1,
        "title": "Immortal 2 account, 42 skins",
        "type": "account",
        "game": "Valorant"
      },
      "quantity": 1,
      "unit_price_cents": 9999,
      "subtotal_cents": 9999,
      "buyer_fee_cents": 0,
      "discount_cents": 0,
      "total_paid_cents": 9999,
      "platform_fee_cents": 999,
      "fee_percent": 10,
      "seller_amount_cents": 9000,
      "currency": "USD",
      "buyer": {
        "id": 2,
        "name": "Rina"
      },
      "buyer_note": null,
      "topup_fields": null,
      "delivery": {
        "delivered_at": "2026-10-26T16:02:10Z",
        "has_credentials": true,
        "proof_count": 0
      },
      "dispute": null,
      "payment_due_at": "2026-10-26T16:54:50Z",
      "paid_at": "2026-10-26T15:54:50Z",
      "delivery_due_at": "2026-10-26T16:24:50Z",
      "delivered_at": "2026-10-26T16:02:10Z",
      "auto_release_at": "2026-10-29T16:02:10Z",
      "completed_at": null,
      "warranty_ends_at": null,
      "created_at": "2026-10-26T15:54:50Z",
      "updated_at": "2026-10-26T15:54:50Z"
    },
    "previous_status": "disputed"
  }
}
order.cancelled
order.cancelled
{
  "id": "evt_01m5wfdf1427b62eba3b29b55a",
  "type": "order.cancelled",
  "created_at": "2026-10-26T15:54:50Z",
  "data": {
    "order": {
      "id": 2,
      "number": "48292852",
      "status": "cancelled",
      "listing": {
        "id": 1,
        "title": "Immortal 2 account, 42 skins",
        "type": "account",
        "game": "Valorant"
      },
      "quantity": 1,
      "unit_price_cents": 9999,
      "subtotal_cents": 9999,
      "buyer_fee_cents": 0,
      "discount_cents": 0,
      "total_paid_cents": 9999,
      "platform_fee_cents": 999,
      "fee_percent": 10,
      "seller_amount_cents": 9000,
      "currency": "USD",
      "buyer": {
        "id": 2,
        "name": "Rina"
      },
      "buyer_note": null,
      "topup_fields": null,
      "delivery": null,
      "dispute": null,
      "payment_due_at": "2026-10-26T16:54:50Z",
      "paid_at": "2026-10-26T15:54:50Z",
      "delivery_due_at": "2026-10-26T16:24:50Z",
      "delivered_at": null,
      "auto_release_at": null,
      "completed_at": null,
      "warranty_ends_at": null,
      "created_at": "2026-10-26T15:54:50Z",
      "updated_at": "2026-10-26T15:54:50Z"
    },
    "previous_status": "paid"
  }
}
order.expired
order.expired
{
  "id": "evt_01m5wf0ed2ee2e155ad487b276",
  "type": "order.expired",
  "created_at": "2026-10-26T15:54:50Z",
  "data": {
    "order": {
      "id": 2,
      "number": "48292852",
      "status": "expired",
      "listing": {
        "id": 1,
        "title": "Immortal 2 account, 42 skins",
        "type": "account",
        "game": "Valorant"
      },
      "quantity": 1,
      "unit_price_cents": 9999,
      "subtotal_cents": 9999,
      "buyer_fee_cents": 0,
      "discount_cents": 0,
      "total_paid_cents": 9999,
      "platform_fee_cents": 999,
      "fee_percent": 10,
      "seller_amount_cents": 9000,
      "currency": "USD",
      "buyer": {
        "id": 2,
        "name": "Rina"
      },
      "buyer_note": null,
      "topup_fields": null,
      "delivery": null,
      "dispute": null,
      "payment_due_at": "2026-10-26T16:54:50Z",
      "paid_at": "2026-10-26T15:54:50Z",
      "delivery_due_at": "2026-10-26T16:24:50Z",
      "delivered_at": null,
      "auto_release_at": null,
      "completed_at": null,
      "warranty_ends_at": null,
      "created_at": "2026-10-26T15:54:50Z",
      "updated_at": "2026-10-26T15:54:50Z"
    },
    "previous_status": "pending_payment"
  }
}
message.created
message.created
{
  "id": "evt_01m5wf2a8c4k1x9t3hq7r0vz6n",
  "type": "message.created",
  "created_at": "2026-10-26T15:54:50Z",
  "data": {
    "order": {
      "id": 2,
      "number": "48292852",
      "status": "processing"
    },
    "message": {
      "id": 3,
      "order_id": 2,
      "sender": {
        "id": 2,
        "name": "Rina",
        "role": "buyer"
      },
      "body": "Hi! Is the account ready?",
      "is_system": false,
      "attachments": [],
      "read_at": null,
      "created_at": "2026-10-26T15:54:50Z"
    }
  }
}
dispute.created
dispute.created
{
  "id": "evt_01m5xsx3ng8f7xxrrennrv9dtd",
  "type": "dispute.created",
  "created_at": "2026-10-27T12:26:56Z",
  "data": {
    "dispute": {
      "id": 1,
      "order": {
        "id": 3,
        "number": "48296203",
        "status": "disputed",
        "listing_title": "Immortal 2 account, 42 skins",
        "total_paid_cents": 11999
      },
      "status": "awaiting_seller",
      "awaiting": "seller",
      "opener_role": "buyer",
      "category": "not_as_described",
      "category_label": "Not as described",
      "reason": "The rank is Ascendant 3, not Immortal 2 as the listing said.",
      "requested_outcome": "partial_refund",
      "requested_amount_cents": 5000,
      "pending_offer": null,
      "resolution": null,
      "resolved_amount_cents": null,
      "resolution_note": null,
      "currency": "USD",
      "escalated_at": null,
      "resolved_at": null,
      "created_at": "2026-10-27T12:26:56Z",
      "updated_at": "2026-10-27T12:26:56Z",
      "offer_count": 0
    },
    "order": {
      "id": 3,
      "number": "48296203",
      "status": "disputed"
    }
  }
}
dispute.updated
dispute.updated
{
  "id": "evt_01m5wf3q1n7d2b5y8kx4c9t0re",
  "type": "dispute.updated",
  "created_at": "2026-10-26T15:54:50Z",
  "data": {
    "dispute": {
      "id": 1,
      "order": {
        "id": 3,
        "number": "48296203",
        "status": "disputed",
        "listing_title": "Immortal 2 account, 42 skins",
        "total_paid_cents": 11999
      },
      "status": "escalated",
      "awaiting": "admin",
      "opener_role": "buyer",
      "category": "not_as_described",
      "category_label": "Not as described",
      "reason": "The rank is Ascendant 3, not Immortal 2 as the listing said.",
      "requested_outcome": "partial_refund",
      "requested_amount_cents": 5000,
      "pending_offer": null,
      "resolution": null,
      "resolved_amount_cents": null,
      "resolution_note": null,
      "currency": "USD",
      "escalated_at": "2026-10-27T13:05:12Z",
      "resolved_at": null,
      "created_at": "2026-10-27T12:26:56Z",
      "updated_at": "2026-10-27T13:05:12Z",
      "offer_count": 0
    },
    "order": {
      "id": 3,
      "number": "48296203",
      "status": "disputed"
    }
  }
}
listing.sold_out
listing.sold_out
{
  "id": "evt_01m5wf4d6p2s8z1w5ja3n7k9qb",
  "type": "listing.sold_out",
  "created_at": "2026-10-26T15:54:50Z",
  "data": {
    "listing": {
      "id": 1,
      "title": "Immortal 2 account, 42 skins",
      "type": "account",
      "status": "sold_out",
      "stock": 0,
      "price_cents": 9999,
      "currency": "USD"
    },
    "order": {
      "id": 2,
      "number": "48292852"
    }
  }
}
ping
ping
{
  "id": "evt_01m5weqvzcma48d9m8tfm3bjv9",
  "type": "ping",
  "created_at": "2026-10-26T15:54:50Z",
  "data": {
    "message": "Webhook test from MythicDeals.",
    "endpoint_id": 1
  }
}

The request

Each delivery is an HTTP POST with these headers and a JSON envelope. data depends on the event.

HeaderValue
Content-Typeapplication/json
User-AgentMythicDeals-Webhooks/1.0
MythicDeals-EventThe event type, e.g. order.paid.
MythicDeals-DeliveryUnique id of this delivery (dlv_…). It stays the same on every retry — use it to drop duplicates.
MythicDeals-Signaturet=<unix time>,v1=<signature> — see below.
JSON
{
  "id": "evt_01m5weqw10bhte2s9tvxk5kqzf",
  "type": "order.paid",
  "created_at": "2026-10-26T15:54:50Z",
  "data": { … }
}

id identifies the event: if the same event goes to two of your endpoints, both get the same id but different MythicDeals-Delivery values.

Verify the signature

Anyone can send a request to your URL, so check that it really comes from MythicDeals before trusting it. The signature is an HMAC-SHA256, in hex, of the timestamp, a dot and the raw request body, using your endpoint secret as the key:

Signature
signed_payload = t + "." + raw_body
v1 = hex(HMAC_SHA256(key = endpoint_secret, message = signed_payload))
  1. Split the MythicDeals-Signature header on , and take t and every v1.
  2. Reject the request if t is more than 5 minutes away from your clock (stops replays).
  3. Compute the HMAC over the bytes exactly as received — before any JSON parsing — and compare it with v1 using a constant-time comparison.
use Illuminate\Http\Request;

// routes/web.php or routes/api.php -- exclude this URL from CSRF.
Route::post('/webhooks/mythicdeals', function (Request $request) {
    $payload = $request->getContent();                 // raw body, unparsed
    $header  = (string) $request->header('MythicDeals-Signature');
    $secret  = config('services.mythicdeals.webhook_secret'); // whsec_...

    abort_unless(mythicdeals_verify($header, $payload, $secret), 400, 'Bad signature');

    $delivery = $request->header('MythicDeals-Delivery');
    if (cache()->add("mythicdeals:{$delivery}", true, now()->addDays(3))) {
        ProcessMythicDealsEvent::dispatch(json_decode($payload, true)); // queue it
    }

    return response()->noContent(); // 2xx fast
});

function mythicdeals_verify(string $header, string $payload, string $secret, int $tolerance = 300): bool
{
    $t = null;
    $signatures = [];
    foreach (explode(',', $header) as $part) {
        [$k, $v] = array_pad(explode('=', trim($part), 2), 2, '');
        if ($k === 't' && ctype_digit($v)) $t = (int) $v;
        if ($k === 'v1') $signatures[] = $v;
    }
    if ($t === null || ! $signatures || abs(time() - $t) > $tolerance) {
        return false;
    }
    $expected = hash_hmac('sha256', $t . '.' . $payload, $secret);
    foreach ($signatures as $sig) {
        if (hash_equals($expected, $sig)) return true;
    }
    return false;
}

Test your code with this known-good example (ignore the 5-minute window for this test):

secretwhsec_test_8bQz1mN4vK7pR2xW9cY5tH3jL6sD0fGa
body{"id":"evt_01m5weqvzcma48d9m8tfm3bjv9","type":"ping","created_at":"2026-10-26T15:54:50Z","data":{"message":"Webhook test from MythicDeals.","endpoint_id":1}}
MythicDeals-Signaturet=1792857290,v1=55812847f0e6020cc7a8cb03d70c8cd855e3050851d090bd796cdb10afba68ec

Responses, retries and auto-disable

  • Answer with any 2xx status within 10 seconds (5 seconds to connect). Anything else — 3xx, 4xx, 5xx, a timeout or a network error — counts as a failure. Redirects are not followed.
  • A failed delivery is retried after 1 min, 5 min, 30 min, 2 h, 6 h, 12 h: 7 attempts in total over about 20 h 36 min. After the last attempt the delivery is marked failed.
  • You can see every delivery, its payload, your response code and the next attempt on the Webhooks page or with GET /webhooks/{webhook}/deliveries, and retry a failed one immediately (POST …/deliveries/{delivery}/retry).
  • After 20 failed attempts in a row the endpoint is turned off and we email you. Fix your server, then turn it back on — this resets the failure count. A single success also resets it.
  • ping test events are sent once, never retried, and never count as failures.

URL rules (SSRF protection)

To protect our network and yours, endpoint URLs must:

  • use https:// and be at most 500 characters;
  • not contain a username or password (https://user:pass@…);
  • resolve only to public IP addresses. Private, loopback, link-local, reserved and carrier-grade NAT ranges (e.g. 10.0.0.0/8, 127.0.0.0/8, 169.254.0.0/16, 192.168.0.0/16, 100.64.0.0/10, ::1, fc00::/7, fe80::/10) are refused.

The address is checked when you save the endpoint and again before every delivery, and the connection is pinned to the IP that was checked, so a DNS change cannot point a delivery at an internal address.

Best practices

  • Verify the raw body. Frameworks that parse JSON first may change spacing or key order and break the signature. Read the raw bytes, verify, then parse.
  • Answer fast, work later. Return 2xx as soon as the signature checks out, and do the real work (delivering, syncing stock) in a queue. Slow answers are timeouts, and timeouts are retried.
  • Drop duplicates. Retries and network hiccups mean you may get the same delivery more than once. Store each MythicDeals-Delivery you processed and skip repeats.
  • Do not rely on order. Events can arrive out of order (a retry of order.paid may arrive after order.delivered). Use previous_status, compare updated_at, or fetch the current state with GET /orders/{order} before acting.
  • Keep the secret secret. If it leaks, rotate it (Webhooks page or PATCH /webhooks/{webhook} with rotate_secret). The old secret stops working at once; deliveries that fail meanwhile are retried, so update your server quickly.
  • Subscribe only to what you use, and use one endpoint per system so a slow tool does not get the others disabled.