Use the hosted PIN-entry iframe
1
Configure your platform
Set Use the canonical HTTPS origin of the page that hosts the iframe: scheme, host, and optional port, without a path, trailing slash, credentials, query, or fragment. One origin covers all user-specific paths on that website. Omit this setting in later configuration updates to preserve it, or set it to
cardConfigs.pinTargetOrigin once through PATCH /platform/config:null to clear it. Changes apply to newly requested URLs.2
Get a PIN-entry URL
Call
GET /cards/{id}/set-pin-url from your backend immediately before displaying the form. Send no request body or origin parameter; Grid uses your platform’s configured origin. If the origin is unset, the request returns 409 CONFLICT.Grid returns iframeUrl, sessionToken, expiresAt, and environment. The URL already contains the temporary token and selects the appropriate environment.Each request creates a fresh, temporary PIN-entry session and returns Cache-Control: no-store. Requesting a URL does not set or change the PIN.3
Load the form
Before setting the iframe’s
src, register a message listener on your page. Only accept messages whose origin equals new URL(iframeUrl).origin and whose source equals your iframe’s contentWindow; also require data.embedType to equal PIN_SETTING.Set the iframe’s src to iframeUrl exactly as returned. Wait for data.messageType to be Embed:Rendered before enabling your submit button. The cardholder enters a four-digit PIN directly into this secure form, so the plaintext PIN never reaches your servers or Grid’s.4
Submit the PIN
When the cardholder clicks your submit button, send
{ messageType: "Embed:SubmitPin", embedType: "PIN_SETTING" } to the iframe’s contentWindow using postMessage. Set the target origin to new URL(iframeUrl).origin; never use *. Disable the button while submission is in progress.5
Confirm the result
Listen for
Embed:PinSubmissionStatusChanged with the following data.status values:Remove your event listener when you remove the iframe. If you stop waiting before a result arrives, treat the outcome as unknown: do not report success or automatically resubmit. An
OK status alone cannot prove that an already configured PIN was changed.expiresAt. Request a new URL for another PIN change or after expiration. Treat both the URL and token as secrets: never persist, cache, log, or send them to analytics.
Encrypt a PIN in your own UI
If you collect the PIN in your own UI, you must encrypt it in the customer’s browser or mobile app before sending it to your server or Grid. Download the PIN encryption public key. This PEM-encoded RSA public key is the same for sandbox and production; save it aspin-encryption-public-key.pem in your client integration.
- Collect a four-digit PIN as a string so leading zeros are preserved.
- Generate a fresh, cryptographically random integer
noncefor each request. - Serialize an object containing
nonceandpinas JSON. Encode the JSON as UTF-8. - Encrypt those bytes using the PIN encryption public key, then base64-encode the ciphertext.
- Send the base64 string as
encryptedPinBlockinPOST /cards/{id}/set-pin.
0123 produces JSON with this shape before encryption:
400 INVALID_INPUT. Check the payload, encoding, and public key, then create a new encrypted block with a fresh nonce. A 204 response means the PIN change was accepted; read pinStatus from GET /cards/{id} to check its resulting state.
Check and recover a PIN
ReadpinStatus from GET /cards/{id}:
Three consecutive incorrect attempts block an online PIN. Call
POST /cards/{id}/pin/unblock to keep the same PIN, or use either PIN-entry flow to choose a new one. Unblocking a PIN that is already OK is safe; a card with no PIN returns 409 CONFLICT.
There is no PIN-reveal API. If the cardholder forgets the PIN, let them set a new one. Setting a PIN does not activate a closed or frozen card or change its spending limits.
Card.pinStatus is the last known PIN status, populated when a card with PIN management is created. An absent value means PIN management is unavailable for this card; it does not mean NOT_SET or tell you whether a payment requires a PIN.
Receive PIN status changes
HandleCARD.PIN_STATUS_CHANGE at your webhook endpoint to keep your app’s PIN status up to date. The event contains the updated Card resource in data, including pinStatus.
NOT_SET → OK: a PIN was configured.OK → BLOCKED: the PIN became blocked.BLOCKED → OK: the PIN was unblocked or replaced.
OK do not trigger it. Use the iframe submission result or PIN API response to confirm a PIN replacement.
Verify the webhook signature and deduplicate deliveries by event id. Deliveries may arrive out of order; use data.updatedAt to avoid overwriting a newer card snapshot. See Card webhooks for payloads and retry behavior.