For all products (both paid and free), you can take advantage of placeholders.
You can verify that these placeholders are authentic by using the
/v1/verifyPurchase API Action.
For paid products, every placeholder will have a value. For free products, only some placeholders are replaced.
Getting started for premium plugins
We recommend new premium plugins use the
/v1/verifyPurchase API action
along with the
%%__LICENSE__%% placeholder for the easiest, and
safest integration.
We recommend that you also use Polymart's automatic obfuscator to combat piracy. The obfuscator will
automatically encrypt all of the placeholders in your .jar file, among other things, making it much
harder to pirate your plugin.
Remember that anyone can modify a file
once it's downloaded, so there's no way to completely stop someone who's truly determined and has
the right skill sets — but, we can make their lives so hard that they don't even try.
Here's yet another thing that other marketplaces can't do: Every placeholder on this page will get replaced with it's respective value, no matter where it is. It can
be in a .jar file, inside of a compressed .zip upload, or even inside of a .jar file that's inside of a .zip file
that's inside of another .zip file that's inside of a .jar file. Cool, huh?
%%__USER__%%Replaced with the user's ID.
The user's profile at https://polymart.org/user/%%__USER__%%.
This will always have a value for paid products.
For free products, this will be "-1" if the user isn't logged in.
%%__USERNAME__%%Replaced with the user's username on Polymart.
Keep in mind that users can change their usernames, so if you need to uniquely identify a user,
you should use %%__USER__%% instead.
This will always have a value for paid products.
For free products, this will be "-1" if the user isn't logged in.
%%__PRODUCT__%% or %%__RESOURCE__%%Replaced with your products's ID on
Polymart. The product is at https://polymart.org/product/%%__PRODUCT__%%
This will always be replaced in all products. The legacy placeholder %%__RESOURCE__%% also works in place of %%__PRODUCT__%%
%%__NONCE__%%Replaced with a unique identifier for the download
This will only be replaced in paid products
%%__PRODUCT_VERSION__%% or %%__RESOURCE_VERSION__%%Replaced with your products's ID on
Replaced with the version of your
resource that was downloaded from Polymart. This is the version on Polymart, and will only match
what you have in the plugin.yml if you give the two the same value. If you want your plugin.yml to always have the same
version number as what you upload to Polymart, set the "version" in the plugin.yml to "%%__RESOURCE_VERSION__%%"
This will always be replaced in all products
%%__UPLOAD__%%Replaced with the ID of your file upload to Polymart.
This is useful mostly for the API action
/v1/downloadLatestUpdate
This will always be replaced in all products
%%__VERIFY_TOKEN__%%Replaced with a token that can be used
to verify the download using the
/v1/verifyPurchase API action.
If you want to make use of the API action, you'll also need to include all of the placeholders on this page.
Or, you can simply use the %%__LICENSE__%% placeholder instead.
This will only be replaced in paid products
%%__LICENSE__%%Replaced with a license key that can be used
to verify the download using the
/v1/verifyPurchase API action. You can also just hide
this placeholder in your products — if you find a leaked copy, the license will uniquely identify the user that
downloaded the resource.
Note that this placeholder may contain characters that are not considered url-safe
This will only be replaced in paid products
%%__LICENSE_n__%%
(where n is between 1 and 10, Polymart pro required)
Similar to %%__LICENSE__%%, but each generated license will be different. This is useful for hiding
multiple different license codes in your resource, and
this can be used to verify the download using the
/v1/verifyPurchase API action.
For example, %%__LICENSE_1__%% or %%__LICENSE_7__%%.
Polymart pro is required to use this placeholder (anyone can use the normal %%__LICENSE__%%)
This will only be replaced in paid products
%%__PHRASE__%% and
%%__PHRASE_n__%%
(where n is between 1 and 10, Polymart pro required)
Replaced with a short, unique English phrase that can be used to track down who downloaded the resource.
You can hide this placeholder in your products — if you find a leaked copy, the license will uniquely identify the user that
downloaded the resource. Because it is an English phrase, it is very difficult for would-be leakers to find and remove
this placeholder from your products.
Polymart pro is required to use this placeholder
This will only be replaced in paid products
%%__POLYMART__%%Replaced with a "1".
So, if your resource was downloaded from polymart, this will be replaced with "1". If it
was downloaded from somewhere else, it will stay as "%%__POLYMART__%%".
This will always be replaced in all products
%%__AGENT__%%Replaced with an ID for the agent that downloaded
this resource. If the resource was downloaded directly from Polymart, this will be a string
of one or more 0's. If it was downloaded using a token from the
Developer API,
this will be the ID of
that token.
This will only be replaced in paid products
%%__TIMESTAMP__%%Replaced with the time at which
the resource was downloaded (seconds in
epoch time)
This will always be replaced in all products
If there's any placeholders that you think would be useful, but aren't currently supported,
shoot us a message and we'll add them as soon as possible
Custom Placeholders
You can add unlimited custom placeholders to your product: constant string values, a hash of another placeholder, or an API call to an external license server.
To add a custom placeholder, just edit your product, and scroll down to the "Security" section.
String constant
Use a String constant custom placeholder to inject a string of your choice into your product. One good use of this is to inject a string that you can change without uploading a new version.
SHA-256
Hash custom placeholders will be a SHA-256 hash of another placeholder value. For example, you could make your placeholder be a SHA-256 hash of the user ID %%__USER__%% to detect tampering with your product's code.
This will always be in lowercase hexadecimal format.
HMAC SHA-256
Like a SHA-256, but more secure. This generates an
HMAC SHA-256 string of another placeholder value, using a secret key you provide as the secret.
For example, you could make your placeholder be a HMAC SHA-256 hash of the user ID %%__USER__%% to detect tampering with your product's code, and using an HMAC is more secure.
This will always be in lowercase hexadecimal format.
To deter leakers, we won't go into details on why it's more secure
External API
With this custom placeholder value, we'll make an HTTP call to an external API, and will replace the custom placeholder with the API's response. This can be useful for using
an external license server.
Whenever a user downloads a product with this placeholder, we send a HTTP POST request to the endpoint you provide, with the following parameters JSON-encoded:
{
"user": <user id (%%__USER__%%)>,
"product": <product id (%%__PRODUCT__%%)>,
"nonce": <nonce (%%__NONCE__%%)>,
"license": <license (%%__LICENSE__%%)>,
"placeholder" <the placeholder key, like %%__MY_CUSTOM_PLACEHOLDER__%%>
"time": <current unix timestamp (not related to a placeholder)>
}
None of these values will ever be removed, but note that this API call might change in the future to include more information.
So, your API should be able to ignore unknown keys in the request.
The request will also include a header
X-Polymart-Signature. This header is the
HMAC SHA-256 hash of the HTTP request body, using the product's API signing secret as the secret key.
The product's API signing secret is shown when product authors add an "External API" custom placeholder.
You
must verify that this signature is correct. Otherwise, attackers can
spoof requests to your endpoint.
You should also confirm that the "time" included in the HTTP POST request is reasonable (within 5 minutes of the current time, for example)
to prevent replay attacks.
If the
X-Polymart-Signature header is correct and the "time" value is reasonable, you should respond with
JSON, with "value" set to the replacement value.
{
"value": "My replacement value"
}
The value must be a string, and must be less than 1,000 characters long. If your API doesn't provide a String value for "value", or if it takes more than 5 seconds to respond, the placeholder
will be replaced with a string detailing the failure in the format
ERROR_HTTP_<http response code>_LEN_<response length>.
Test your integration
Custom
We also support custom-coded placeholders for enterprise users. If you have a special use case, please
contact us.
Advanced Placeholders
If you plan to use placeholders for automatic piracy prevention, you must first
test your system and ensure that it doesn't generate false positives. These only work for paid products.
%%__RSA_PUBLIC_KEY__%%Replaced with the
websafe contents of the 2048-bit RSA public key for your
resource. The contents of RSA keys are normally base64-encoded, but remember that Polymart turns the base64 into
websafe base64. Websafe base64-encoding replaces
all "+" with "-", replaces all "/" with "_", and removes all "=" from the original base64 string. Use this public key to decrypt
%%__SIGNATURE__%%.
The key is encoded according to the X.509 standard.
For example, it might look something like this:
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4fcZHVjpsBVjTGxlf2miXfGiCH71eMbEAlhCof-QZOC5CZV1EV03f9ufQIJN6bTOY3TxppB65KImQYHPY1q05hCeM9odkjDVKBZri-HGRcLdXCce_nU-55H1szTESza8JmtCaGn70Y_NOqhCBcr5xjufoCjHzHNnTOwmoNI2dIVD2PAEl7MwL2jqvj0NoMs53NVvW2MA5hLLNUuxpemojOj2HaL8btid0av1Z2MVJsRhB524UVW75xiUQ8InzWO0M4Cn9bsNhOIcKw4BUpBnpRrqM5n9iKr6uk7tLjJNP_6zAT5d3hotPzGSHru2b2Fd-Y_mJ2JuS2dSr6FSR9pU8wIDAQAB
%%__SIGNATURE__%%Replaced with a
web-safe base64-encoded
string. It encodes an encrypted JSON string, which is encrypted using your resource's RSA private key (which only Polymart knows).
The encrypted JSON string contains information about the resource download.
The string can be decrypted using the RSA public key in
%%__RSA_PUBLIC_KEY__%%,
and is
padded using OpenSSL PKCS1.
The JSON string that is encrypted will contain the following information:
{
"ver": "%%__INJECT_VER__%%",
"resource_ver": "%%__RESOURCE_VERSION__%%",
"user": "%%__USER__%%",
"resource": "%%__RESOURCE__%%",
"nonce": "%%__NONCE__%%",
"agent": "%%__AGENT__%%",
"timestamp": "%%__TIMESTAMP__%%"
}
This is then encrypted using your resource's private key. Then, the encrypted string
is encoded as websafe base64. The result might look something like this:
LLK7UqXjRHXotXIhMQSGaDQj2K2i7jDJs6Ief1CR5dHjg03D2e3HcT14rL61Dxq3B2F68y436sbd2HzhYCsP5tNQWaVJxt5YOaXpu1lI75hAiglA_joHBKC5xZc4J9TJrraiyGdQdFMBnNd_2pU9AhL86XxMEiUbtF9SR8IyEdVE2mLP7Fkehgg4P9PRt5mjppMBBggcDM_psmwdXdqK5131OPPbzjkjv4LBUoihAkUsUP35CSXLk0ruymZNcYzDvvzsLeVx2pBlzID6DuGnTcWjfXT1SnaMyBGj3DAzd324-DYGKfp2Dy0BCWwKb0O7VqVYtutOsU381gsy4Cd9OA
You can then decrypt this using your resource's public key,
%%__RSA_PUBLIC_KEY__%%,
to retrieve the above JSON.
If you want to test your system, decrypt the above websafe base64-encoded example with the example
public key given above in
%%__RSA_PUBLIC_KEY__%%.
Remember that OpenSSL PKCS1 padding is used!
You should get this:
{
"ver": "1",
"resource_ver": "1.0.4",
"user": "1",
"resource": "4",
"nonce": "7318",
"agent": "0",
"timestamp": "1595627584"
}