Drink tokens at a fundraiser, meal tickets for a staff lunch, prize draw entries, food stall tokens at a school fair, one free coffee per conference badge: the request is always the same. Each code should work once, and a second attempt should fail. The part most guides skip is where that rule lives. It cannot live in the QR code.
Written by QR to Sheets, which sells the scanner link described in the second half, so weigh that accordingly. The free routes come first and work with any QR code. Google and vendor facts were read on their own pages on 2026-10-08.
Why can't a QR code limit itself to one use?
A QR code is a picture of some text, such as V-0042-7F3A. It has no memory and no way of knowing it was scanned before. A photocopy or a screenshot of it is exactly as valid as the original. So single use is never a property of the code: it is a property of whatever reads it, which has to look the code up in a record of redemptions and refuse it if it is already there. Every working single-use system, free or paid, is that record plus a check.
This is also why the scan limits offered by dynamic QR code generators solve a different problem. A dynamic code points to a web address the generator controls, and the limit counts visits to that address. QR TIGER's help page puts it plainly: "Once a QR code reaches its scan limit, it will no longer redirect to its embedded link." It counts scans of the code, not redemptions, so a guest checking the code at home uses one up, and the person at the counter learns nothing about whether this voucher was already used. Uniqode's own article on one-time-use QR codes describes redemption the same way: the system "marks that credential as consumed, so any further attempts are automatically rejected." The check happens where the code is redeemed.
Make one numbered code per voucher
Whatever records the redemptions, every voucher needs its own value. A hundred copies of the same code cannot be told apart, so one redemption would use up all of them. Build the list in a Google Sheet, on a tab called Issued:
- In A2, a running number: ="V-"&TEXT(ROW()-1, "0000"), filled down to the number of vouchers. Row 2 gives V-0001, row 401 gives V-0400.
- In B2, the same number with a random part that cannot be guessed from the one before it: =A2&"-"&DEC2HEX(RANDBETWEEN(4096, 65535)), filled down. That gives values like V-0042-7F3A.
- Then copy column B and paste it back with Edit, Paste special, Values only. RANDBETWEEN picks new numbers every time the sheet changes, so until you paste the values, the codes keep changing.
Paste column B into the free bulk QR code generator, up to 500 codes at a time, and print them on label sheets or straight onto the tickets. It needs no account, and the codes contain only the text you pasted. Keep the Issued tab: it is the list every redemption is checked against.
Free route: a Google Form, checked with COUNTIF afterwards
The cheapest record is a Google Form with one short-answer question, Voucher code, opened on the phones at the counter. Staff type or paste the code from the voucher, submit, and hand over the drink or the meal. Each submission becomes a row in the linked response sheet, with Google's timestamp in column A and the code in column B. A shared Google Sheet the staff type into directly works the same way, with the same columns. Google Forms adds each response as a new row, so a formula filled down beside the responses does not reach new rows. Put each flag as one formula in the header cell of an empty column instead, and it covers every row:
- A repeat flag, in D1: ={"Repeat"; ARRAYFORMULA(IF(B2:B="", "", IF(COUNTIFS(B2:B, B2:B, ROW(B2:B), "<="&ROW(B2:B))>1, "REPEAT", "")))}. It marks the second and every later redemption of a code, and leaves the first one blank.
- A check against the list you printed, in E1: ={"Issued?"; ARRAYFORMULA(IF(B2:B="", "", IF(COUNTIF(Issued!B:B, B2:B)=0, "NOT ISSUED", "")))}. It catches a mistyped code and a code nobody issued.
- How many different vouchers have been used: =IFERROR(ROWS(UNIQUE(FILTER(UPPER(B2:B), B2:B<>""))), 0). UPPER makes v-0042 and V-0042 count once.
- On the Issued tab, when each voucher was first redeemed: =IFERROR(INDEX(Responses!A:A, MATCH(B2, Responses!B:B, 0)), "Not redeemed"), where Responses is the name of the response tab.
COUNTIF ignores upper and lower case, so v-0042-7f3a and V-0042-7F3A count as the same code, which is what you want here. Google's help notes that a COUNTIF criterion can contain the wildcards ? and *, so keep those characters out of voucher codes.
Where the free route breaks
- It flags, it does not stop. The REPEAT mark appears after the second row is written, by which time the second drink has been poured. It tells you a voucher was used twice; it cannot tell the person at the counter to say no.
- Two staff can both accept the same code. If two counters redeem the same voucher a minute apart, both submissions succeed and both people are served. Looking the code up in the sheet before serving narrows the gap but does not close it, and it is slow with a queue.
- Limit to 1 response limits Google accounts, not codes. Google's help says that with it on, users must sign in to their Google Account to fill out the form, so it stops one staff phone submitting twice, not one voucher being redeemed twice.
- Google Forms has no barcode scanner, so codes are typed. A typo creates a NOT ISSUED row and lets the real code be redeemed again later.
- No signal, no row. A form that cannot submit leaves the counter to remember the code or wave it through.
For a fundraiser with a dozen tokens and one table, that may be entirely fine, and it costs nothing. It stops being fine when vouchers are worth something, when there is more than one counter, or when the queue is long.
How QR to Sheets refuses a second redemption
In QR to Sheets, the admin signs in with Google once, connects a spreadsheet and creates a scanner link that writes to one tab. Counter staff open the link in Safari or Chrome on their phones, allow the camera once and scan vouchers, with no app and no account. Each link has a setting called Accept duplicate scans. For vouchers, switch it off.
- With it off, the link records each code once, ever. The first scan of V-0042-7F3A is written to the Sheet as a row with its scan time.
- A second scan of the same code shows Already scanned, with the time it was first scanned, and nothing is added to the Sheet. The person at the counter sees the refusal on the scan they just made.
- It holds across phones: a voucher redeemed on one counter phone is refused on every other phone using the same link, and if two phones scan the same code at the same moment, only one row is kept.
- Change the setting before the event, then open or refresh the link on each phone, because a phone picks up the link's settings when the scanner page loads. Any code already recorded on the link is refused, including test scans made before you switched the setting off, so rehearse with codes you will not issue, or create a fresh link for the event.
- The link does not know which vouchers you issued. It refuses repeats; it does not refuse a code it has never seen. Keep the NOT ISSUED formula above on a separate tab to catch those.
The trade-off is deliberate: a refused scan leaves no row. The Sheet shows each voucher's first redemption and nothing about later attempts, so it cannot tell you who tried a voucher twice. If you need every attempt on record, leave the setting on and flag repeats in the sheet instead, as handling duplicate QR scans describes.
If the signal drops, the open scanner keeps saving scans on the phone and sends them when the signal is back, each with its original scan time. Without a signal, though, a phone can refuse only a code it has already scanned itself on that link. Any other code shows as recorded on the device, so the counter cannot tell it will be refused later. When the signal returns, the first redemption to reach QR to Sheets is kept and the later one is refused, after the voucher has already been honoured. For vouchers, keep the counter phones online.
One link per voucher type or meal service
The once-ever rule is counted per scanner link. The same code scanned on two different links is recorded on both. That is what lets one printed card serve several redemptions, one per link:
- A staff meal card valid once per day: one link per meal service, named Lunch 12 Oct, Lunch 13 Oct. The link's name is written on every row it records, so the Sheet shows which service each meal came from.
- A festival wristband with one free drink and one meal: a Drinks link and a Meals link, each refusing its own repeats.
- A prize draw with one entry per ticket: one link for the draw, switched off, and the Sheet becomes the list of entries to draw from.
The free plan has one scanner link. Several links, one per service or voucher type, need Premium, which has unlimited links. For more than one counter on the same link, several phones scanning into one Google Sheet covers how rows are told apart.
When not to switch it off
Accept duplicate scans is on by default, and it should stay on for anything a person scans more than once on purpose. Switched off for daily attendance, it would refuse every person's scan tomorrow; switched off on a check-in and check-out link, it would refuse every return. With it on, every scan is written, and a code scanned again on the same link within 12 hours shows an amber Already scanned badge with the count and the words Saved anyway, so the person scanning knows it is a repeat and the record still holds every scan.
| Capability | Google Form + COUNTIF flag (free) | QR to Sheets |
|---|---|---|
| Cost | Free | Free for one phone and 300 scans in total, then $7 per phone per month |
| Second redemption at the counter | Accepted, then flagged REPEAT in the sheet | Refused on the phone with Already scanned and the first scan time |
| Two counters, same voucher, same moment | Both accepted | One row kept, the other refused |
| How the code is entered | Typed into the form | Scanned with the phone camera |
| Record of refused attempts | Every attempt is a row | None: a refused scan leaves no row |
| Codes nobody issued | COUNTIF against the Issued tab | COUNTIF against the Issued tab |
| Who can see the spreadsheet | Whoever you share it with; form users see only the form | Counter staff scan through the link and never get access to the spreadsheet |
What it costs, and when a guest list fits better
Each phone that scans counts as one device. Free covers one phone; Premium is $7 per phone per month. People being scanned never open the link and need nothing. The free plan also has one link and 300 scans in total, not per month, so it suits a trial or a small one-off; 400 drink tokens at two counters is two phones on Premium, $14 for the month. QR to Sheets writes each scan time as text in the form YYYY-MM-DD HH:MM:SS, so formulas that do arithmetic on it wrap it in VALUE().
Vouchers are anonymous: the code says what it is worth, not who holds it. When each code belongs to a named person who registered, the job is check-in against a list instead. Giving every form submission its own QR code covers issuing a personal code to each registration, and the event check-in apps compared page sets the guest-list tools side by side, including QR to Sheets' Events add-on, which costs $25/month and requires Premium.
Step by step
- 1
Give every voucher its own code
Number the vouchers in a sheet, for example V-0001 to V-0400, add a short random part so the next number cannot be guessed, and keep that list as your record of what was issued.
- 2
Print one QR code per voucher
Paste the list into a free bulk QR code generator and print the codes on label sheets or on the tickets themselves. Each code contains only its voucher number.
- 3
Decide where redemptions are recorded
Either a Google Form the counter staff fill in, checked afterwards with a formula, or a scanner link set to record each code once, which refuses the second redemption on the spot.
- 4
Use one scanner link per voucher type
A link records each code once, ever. Give each kind of voucher or each meal service its own link, so a lunch ticket redeemed today does not block tomorrow's.
- 5
Check the log against the issued list
A formula on a separate tab marks any redeemed code that was never issued, and counts how many vouchers have been used so far.
Frequently asked questions
Can a QR code be made to work only once?+
Not by itself. A QR code is printed text with no memory, and a copy of it is as valid as the original. It works once only when whatever scans it checks a record of codes already redeemed and refuses a repeat.
Does a scan limit on a dynamic QR code stop a voucher being used twice?+
No. A dynamic code's scan limit counts scans of the web address behind it, so a guest checking the code at home uses one up. It tells the person at the counter nothing about whether that voucher was already redeemed.
How do I find vouchers redeemed twice in Google Sheets?+
With voucher codes in column B, put ={"Repeat"; ARRAYFORMULA(IF(B2:B="", "", IF(COUNTIFS(B2:B, B2:B, ROW(B2:B), "<="&ROW(B2:B))>1, "REPEAT", "")))} in the header cell of an empty column, so it also covers responses that arrive later. It marks every redemption after the first. It finds repeats after they happen; it cannot stop one at the counter.
Can QR to Sheets refuse a voucher that was already scanned?+
Yes. Switch off Accept duplicate scans on the scanner link. That link then records each code once, ever, and a repeat shows Already scanned with the time it was first scanned, with nothing added to the Sheet, even when two phones scan the same code at the same moment.
Is a refused scan recorded anywhere?+
No. With Accept duplicate scans off, a refused scan leaves no row, so the Sheet holds each code's first redemption only. To keep every attempt, leave the setting on: repeats are then written and flagged with an amber Already scanned badge.
Can the same card be redeemed once a day?+
Yes, with one scanner link per day or per meal service. The once-only rule is counted per link, so a card scanned on the Lunch 12 Oct link can still be scanned once on the Lunch 13 Oct link. Several links need Premium; the free plan has one.