Technical integration guide
Everything needed to wire Verified Draws into a site: connect the plugin with a site token, register each competition against a sealed seed commitment, allocate provably-fair ticket numbers instantly per order, reveal the seed at close, pull results back through the endpoint contract, and render the stored verify links in your own theme.
Download the plugin zip from the /api/plugin/download endpoint, then upload it under Plugins → Add New → Upload in WordPress. Open Verified Draws → Settings and run the connection self-test.
The site key (site token) is generated locally on first use. It is sent only server-to-server over HTTPS as a Bearer token — never shown in a browser or placed in a URL. Each competition is registered once against POST /api/plugin/competition, which is idempotent per (site, competitionKey).
Three calls run a competition end to end. All are authenticated with the Bearer site token and all are idempotent, so retries are always safe.
- Register —
POST /api/plugin/competition. Verified Draws mints a secret ticket seed server-side and answers ready immediatelywith the competition's publicpublicCodeand itsticketSeedCommitment— the sha256 of that seed (schemevd-committed-v2). The seed itself is never returned; publishing its hash up front is what seals the number mapping before the first ticket sells. - Allocate —
POST /api/plugin/tickets. Assigns blocks of ticket numbers on demand, instantly — there is no waiting on a randomness round. Send{ count }to allocate the next block (an optionalIdempotency-Keyheader makes retries replay the same window), or{ positions }to re-issue freed positions after a refund. - Close —
POST /api/plugin/competition/close. Halts the competition and reveals the seed. From that moment the competition's verify page returns the seed plus three self-contained scripts (Node, Python, PHP) any buyer can run fully offline to recompute their exact numbers.
curl -X POST https://verifieddraws.uk/api/plugin/competition \
-H "Authorization: Bearer $VD_SITE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"competitionKey":"woo:1234","rangeMin":1,"rangeMax":5000,"prefix":"A","suffix":"-UK","padWidth":4,"label":"Summer Cash Draw"}'{
"status": "ready",
"publicCode": "VCODE-7Q2F",
"ticketSeedCommitment": "9f1a4c…e2b7 (sha256 of the secret seed)",
"rangeMin": 1,
"rangeMax": 5000,
"prefix": "A",
"suffix": "-UK",
"padWidth": 4
}One optional register field, hasInstantWins (boolean), declares that the competition carries instant-win prizes. It is set by RaffleForge-hosted competitions — instant wins are a hosted-platform feature. The flag is monotonic: once registered as true it stays true across re-registration, so a later push can never quietly hide a declared instant-win section. Numbers-only integrations, including the WordPress plugin, omit it, and the competition's verify page then shows no instant-win section at all.
Competitions registered before this scheme used the legacy drand-v1 flow — a committed future drand round plus a secret salt, answering 202 pending until the round published. Those competitions still verify exactly as before; every new registration is vd-committed-v2 and is ready the instant it is created.
The final entrant roster is pushed to POST /api/ingest, authenticated with the Bearer site token. It sends only ticket values — numeric tickets only; a shared prefix or suffix like A001-UK is fine, but text or name rosters are rejected at ingest. It never sends payment details, addresses, or emails.
Ticket numbers are assigned the moment an order is paid — allocation is instant, with no waiting on a future randomness round. The competition's secret seed is minted by Verified Draws at registration and never leaves the server; while the competition sells, only its sha256 commitment (ticketSeedCommitment) is public. Tickets are positions in a seed-keyed permutation over the number range, computed server-side by POST /api/plugin/tickets: deterministic, tamper-evident, and unpredictable while selling — the commitment published before the first sale seals the mapping, and the seed revealed at close proves it. Assignment is keyed on the order id (get-or-create / idempotent). When an order is refunded or cancelled, its contiguous block of ticket positions returns to a reuse pool and is reissued to the next purchase — so refunded numbers stay in play and the draw space stays tight, while assignment stays idempotent per order id.
$assignment = vd_assign_tickets(
$competition_id, // WP post id of the registered competition
$order_id, // the buyer's order — the idempotency key
$count // how many tickets to assign
);
$assignment->tickets; // e.g. ["A001-UK", "A002-UK"]
$assignment->start_position; // first block index for this order
$assignment->verify_url; // /verify/order/<code>-<start>-<count>The per-order verify link follows the format https://verifieddraws.uk/verify/order/<code>-<start>-<count>.
https://verifieddraws.uk/verify/order/VCODE-7Q2F-0-3Every per-order verify link resolves to one of two tiers, decided by the competition's lifecycle — plus an instant-win panel that appears only where it applies.
- OPEN — while selling. The page is confirm-only: a buyer pastes the tickets they hold and gets a match verdict against the sealed order. The seed is never shown and numbers cannot be enumerated. The page also shows the
ticketSeedCommitmentpublished at registration, so anyone can record the commitment before the reveal. - CLOSED — after close. The seed is revealed. The page recomputes the buyer's exact numbers, checks that
sha256(seed)matches the published commitment, and hands over three self-contained scripts (Node, Python, PHP) that recompute the numbers fully offline — trusting no one, not even Verified Draws. - Instant-win map. Shown only on competitions that actually have instant wins (RaffleForge-hosted): the published prize map is checked against its public commitment — green when the recomputation matches, red if the map is missing or has been altered. Numbers-only competitions, including everything run through the WordPress plugin, show no instant-win section at all.
The endpoint is POST /api/plugin/results, authenticated with the Bearer site token. The request body takes three forms:
{ "sourceId": "adapter:ref" }— a single competition. Returns{ competition, draws }(404 if not found or not owned by this install).{ "sourceIds": ["...","..."] }— a batch over an explicit list. Returns{ competitions: [ { competition, draws }, ... ] }.- empty / absent body
{}— a batch over all of this install's competitions. Returns{ competitions: [...] }. This is what the plugin's one-click "Pull verification results" button uses.
curl -X POST https://verifieddraws.uk/api/plugin/results \
-H "Authorization: Bearer $VD_SITE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"sourceId":"woo:1234"}'curl -X POST https://verifieddraws.uk/api/plugin/results \
-H "Authorization: Bearer $VD_SITE_TOKEN" \
-H "Content-Type: application/json" \
-d '{}'{
"competition": { "name": "Summer Cash Draw", "sourceId": "woo:1234" },
"draws": [
{
"rollIndex": 0,
"winner": { "number": 42, "ticket": "A042-UK" },
"publicCode": "VCODE-7Q2F",
"verifyUrl": "https://verifieddraws.uk/verify/VCODE-7Q2F",
"verified": true,
"revealedAt": "2026-06-13T20:14:00.000Z",
"skipped": [ { "number": 7, "ticket": "A007-UK" } ]
}
]
}In the API response, skipped is an array of objects { number, ticket }: the unsold / generated numbers the draw skipped before landing on a sold ticket.
The plugin persists the pulled payload as a JSON string in post meta under the key _vd_results. One important nuance: when stored by the plugin, each draw's skipped is normalised to a string[] of full ticket strings — not objects.
{
"competition": { "name": "Summer Cash Draw", "sourceId": "woo:1234" },
"draws": [
{
"rollIndex": 0,
"winner": { "number": 42, "ticket": "A042-UK" },
"publicCode": "VCODE-7Q2F",
"verifyUrl": "https://verifieddraws.uk/verify/VCODE-7Q2F",
"verified": true,
"revealedAt": "2026-06-13T20:14:00.000Z",
"skipped": [ "A007-UK", "A019-UK" ]
}
]
}The default path: automatic on-page display
Out of the box, the plugin renders the winning ticket and verify link on each competition (WooCommerce product) page the moment results are pulled — via woocommerce_after_single_product_summary on classic themes and a render_block filter on block themes. The operator controls this with the Show results on competition page toggle (option vd_show_results_on_competition, default on) under Verified Draws → Settings. Turn it off when you want to place the results yourself with one of the options below.
The easy path: the shortcode
Drop the [vd_results] shortcode into the page. An optional id attribute targets a specific post. The same markup is also auto-appended to the post content via the_content as a manual fallback.
[vd_results]
[vd_results id="1234"]The custom path: read the two fields yourself
Read _vd_results from post meta, decode the JSON, and loop draws. For each draw there are exactly two fields you need to render anywhere in a custom theme:
- The winning ticket —
draws[].winner.ticket(e.g."A042-UK"). The drawn ticket string to show as the result. - The verify link —
draws[].verifyUrl(e.g.https://verifieddraws.uk/verify/VCODE-7Q2F). The public, shareable URL anyone can open to confirm the draw against the beacon.
In the PHP below those two fields are read as $draw['winner']['ticket'] and $draw['verifyUrl'].
<?php
$raw = get_post_meta( get_the_ID(), '_vd_results', true );
$data = $raw ? json_decode( $raw, true ) : null;
if ( $data && ! empty( $data['draws'] ) ) :
echo '<div class="vd-results">';
foreach ( $data['draws'] as $draw ) :
$ticket = esc_html( $draw['winner']['ticket'] ?? '' );
$verify = esc_url( $draw['verifyUrl'] ?? '' );
?>
<p class="vd-results__winner">
Winning ticket: <strong><?php echo $ticket; ?></strong>
<a href="<?php echo $verify; ?>" rel="nofollow noopener" target="_blank">Verify this draw</a>
</p>
<?php if ( ! empty( $draw['skipped'] ) ) : ?>
<details>
<summary>Unsold / generated tickets skipped</summary>
<ul>
<?php foreach ( (array) $draw['skipped'] as $sk ) : ?>
<li><?php echo esc_html( (string) $sk ); ?></li>
<?php endforeach; ?>
</ul>
</details>
<?php endif;
endforeach;
echo '</div>';
endif;
?>The winner verify link points at /verify/<code> and the per-order ticket link at /verify/order/<code>-<start>-<count> — both are already public.
Build a Past Winners page in your theme
The single-page recipe above shows one competition's result on its own page. To build a site-wide “Past Winners” listing, run a WP_Query for every post that carries the _vd_results meta key, decode each stored payload, and render the competition name, the winning ticket (draws[].winner.ticket) and its verify link (draws[].verifyUrl). One query, one loop, every drawn competition on the site.
<?php
// Past Winners — list every competition that has a stored Verified Draws result.
$winners = new WP_Query( array(
'post_type' => 'product', // your competition post type
'posts_per_page' => -1,
'meta_key' => '_vd_results',
'meta_compare' => 'EXISTS',
'orderby' => 'date',
'order' => 'DESC',
) );
if ( $winners->have_posts() ) :
echo '<ul class="vd-past-winners">';
while ( $winners->have_posts() ) : $winners->the_post();
$data = json_decode( get_post_meta( get_the_ID(), '_vd_results', true ), true );
if ( empty( $data['draws'] ) ) { continue; }
foreach ( $data['draws'] as $draw ) :
$ticket = esc_html( $draw['winner']['ticket'] ?? '' );
$verify = esc_url( $draw['verifyUrl'] ?? '' );
?>
<li class="vd-past-winners__item">
<span class="vd-past-winners__comp"><?php the_title(); ?></span>
<span class="vd-past-winners__ticket">Winning ticket: <strong><?php echo $ticket; ?></strong></span>
<a class="vd-past-winners__verify" href="<?php echo $verify; ?>" rel="nofollow noopener" target="_blank">Verify this draw</a>
</li>
<?php
endforeach;
endwhile;
echo '</ul>';
wp_reset_postdata();
endif;
?>The meta_compare => 'EXISTS' clause selects only competitions that have already been drawn and pulled back, so the list stays clean. Each draws[].verifyUrl is a public, shareable link anyone can open to confirm the draw against the beacon.